PageRenderTime 139ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 2ms

/ajax/libs/breezejs/1.2.5/breeze.debug.js

https://bitbucket.org/kolbyjAFK/cdnjs
JavaScript | 13133 lines | 7750 code | 1149 blank | 4234 comment | 1306 complexity | b12e12e890b396819dfcc269bbbf6c6a MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
  1. /*
  2. * Copyright 2012 IdeaBlade, Inc. All Rights Reserved.
  3. * Use, reproduction, distribution, and modification of this code is subject to the terms and
  4. * conditions of the IdeaBlade Breeze license, available at http://www.breezejs.com/license
  5. *
  6. * Author: Jay Traband
  7. */
  8. (function (definition) {
  9. // CommonJS
  10. if (typeof exports === "object") {
  11. module.exports = definition();
  12. // RequireJS
  13. } else if (typeof define === "function") {
  14. define(definition);
  15. // <script>
  16. } else {
  17. breeze = definition();
  18. }
  19. })(function () {
  20. var breeze = {
  21. version: "1.2.5",
  22. };
  23. // legacy properties - will not be supported after 6/1/2013
  24. breeze.entityModel = breeze;
  25. breeze.entityTracking_backingStore = "backingStore";
  26. breeze.entityTracking_ko = "ko";
  27. breeze.entityTracking_backbone = "backbone";
  28. breeze.remoteAccess_odata = "odata";
  29. breeze.remoteAccess_webApi = "webApi";
  30. /**
  31. @module core
  32. **/
  33. var __hasOwnProperty = Object.prototype.hasOwnProperty;
  34. // iterate over object
  35. function __objectForEach(obj, kvFn) {
  36. for (var key in obj) {
  37. if (__hasOwnProperty.call(obj, key)) {
  38. kvFn(key, obj[key]);
  39. }
  40. }
  41. }
  42. function __objectFirst(obj, kvPredicate) {
  43. for (var key in obj) {
  44. if (__hasOwnProperty.call(obj, key)) {
  45. var value = obj[key];
  46. if (kvPredicate(key, value)) {
  47. return { key: key, value: value };
  48. }
  49. }
  50. }
  51. return null;
  52. };
  53. function __objectMapToArray(obj, kvFn) {
  54. var results = [];
  55. for (var key in obj) {
  56. if (__hasOwnProperty.call(obj, key)) {
  57. var result = kvFn(key, obj[key]);
  58. if (result) {
  59. results.push(result);
  60. }
  61. }
  62. }
  63. return results;
  64. }
  65. // Not yet needed
  66. //// transform an object's values
  67. //function objectMapValue(obj, kvProjection) {
  68. // var value, newMap = {};
  69. // for (var key in obj) {
  70. // if (__hasOwnProperty.call(obj, key)) {
  71. // value = kvProjection(key, obj[key]);
  72. // if (value !== undefined) {
  73. // newMap[key] = value;
  74. // }
  75. // }
  76. // }
  77. // return newMap;
  78. //}
  79. //// shrink an object's surface
  80. //function objectFilter(obj, kvPredicate) {
  81. // var result = {};
  82. // for (var key in obj) {
  83. // if (__hasOwnProperty.call(obj, key)) {
  84. // var value = obj[key];
  85. // if (kvPredicate(key, value)) {
  86. // result[key] = value;
  87. // }
  88. // }
  89. // }
  90. // return result;
  91. //};
  92. // Functional extensions
  93. // can be used like: persons.filter(propEq("firstName", "John"))
  94. function __propEq(propertyName, value) {
  95. return function (obj) {
  96. return obj[propertyName] === value;
  97. };
  98. }
  99. // can be used like persons.map(pluck("firstName"))
  100. function __pluck(propertyName) {
  101. return function (obj) { return obj[propertyName]; };
  102. }
  103. // end functional extensions
  104. function __getOwnPropertyValues(source) {
  105. var result = [];
  106. for (var name in source) {
  107. if (__hasOwnProperty.call(source, name)) {
  108. result.push(source[name]);
  109. }
  110. }
  111. return result;
  112. }
  113. function __extend(target, source) {
  114. if (!source) return target;
  115. for (var name in source) {
  116. if (__hasOwnProperty.call(source, name)) {
  117. target[name] = source[name];
  118. }
  119. }
  120. return target;
  121. }
  122. function __toJson(source, addlPropNames) {
  123. var target = {};
  124. for (var name in source) {
  125. if (__hasOwnProperty.call(source, name) && name.substr(0, 1) != "_") {
  126. var value = source[name];
  127. if (__isFunction(value)) continue;
  128. if (typeof(value) === "object") {
  129. if (value && value.parentEnum) {
  130. target[name] = value.name;
  131. }
  132. } else {
  133. target[name] = value;
  134. }
  135. }
  136. }
  137. addlPropNames && addlPropNames.forEach(function(n) {
  138. target[n] = source[n];
  139. });
  140. return target;
  141. }
  142. // array functions
  143. function __arrayFirst(array, predicate) {
  144. for (var i = 0, j = array.length; i < j; i++) {
  145. if (predicate(array[i])) {
  146. return array[i];
  147. }
  148. }
  149. return null;
  150. }
  151. function __arrayIndexOf(array, predicate) {
  152. for (var i = 0, j = array.length; i < j; i++) {
  153. if (predicate(array[i])) return i;
  154. }
  155. return -1;
  156. }
  157. function __arrayRemoveItem(array, predicateOrItem) {
  158. var predicate = __isFunction(predicateOrItem) ? predicateOrItem : undefined;
  159. var l = array.length;
  160. for (var index = 0; index < l; index++) {
  161. if (predicate ? predicate(array[index]) : (array[index] === predicateOrItem)) {
  162. array.splice(index, 1);
  163. return index;
  164. }
  165. }
  166. return -1;
  167. }
  168. function __arrayZip(a1, a2, callback) {
  169. var result = [];
  170. var n = Math.min(a1.length, a2.length);
  171. for (var i = 0; i < n; ++i) {
  172. result.push(callback(a1[i], a2[i]));
  173. }
  174. return result;
  175. }
  176. function __arrayDistinct(array) {
  177. array = array || [];
  178. var result = [];
  179. for (var i = 0, j = array.length; i < j; i++) {
  180. if (result.indexOf(array[i]) < 0)
  181. result.push(array[i]);
  182. }
  183. return result;
  184. }
  185. // Not yet needed
  186. //// much faster but only works on array items with a toString method that
  187. //// returns distinct string for distinct objects. So this is safe for arrays with primitive
  188. //// types but not for arrays with object types, unless toString() has been implemented.
  189. //function arrayDistinctUnsafe(array) {
  190. // var o = {}, i, l = array.length, r = [];
  191. // for (i = 0; i < l; i += 1) {
  192. // var v = array[i];
  193. // o[v] = v;
  194. // }
  195. // for (i in o) r.push(o[i]);
  196. // return r;
  197. //}
  198. function __arrayEquals(a1, a2, equalsFn) {
  199. //Check if the arrays are undefined/null
  200. if (!a1 || !a2) return false;
  201. if (a1.length != a2.length) return false;
  202. //go thru all the vars
  203. for (var i = 0; i < a1.length; i++) {
  204. //if the var is an array, we need to make a recursive check
  205. //otherwise we'll just compare the values
  206. if (Array.isArray( a1[i])) {
  207. if (!__arrayEquals(a1[i], a2[i])) return false;
  208. } else {
  209. if (equalsFn) {
  210. if (!equalsFn(a1[i], a2[i])) return false;
  211. } else {
  212. if (a1[i] != a2[i]) return false;
  213. }
  214. }
  215. }
  216. return true;
  217. }
  218. // end of array functions
  219. function __requireLib(libNames, errMessage) {
  220. var arrNames = libNames.split(";");
  221. for (var i = 0, j = arrNames.length; i < j; i++) {
  222. var lib = __requireLibCore(arrNames[i]);
  223. if (lib) return lib;
  224. }
  225. throw new Error("Unable to initialize " + libNames + ". " + errMessage || "");
  226. }
  227. function __requireLibCore(libName) {
  228. var lib = window[libName];
  229. if (lib) return lib;
  230. if (window.require) {
  231. lib = window.require(libName);
  232. }
  233. if (lib) return lib;
  234. return null;
  235. }
  236. function __using(obj, property, tempValue, fn) {
  237. var originalValue = obj[property];
  238. if (tempValue === originalValue) {
  239. return fn();
  240. }
  241. obj[property] = tempValue;
  242. try {
  243. return fn();
  244. } finally {
  245. obj[property] = originalValue;
  246. }
  247. }
  248. function __wrapExecution(startFn, endFn, fn) {
  249. var state;
  250. try {
  251. state = startFn();
  252. return fn();
  253. } catch (e) {
  254. if (typeof(state) === 'object') {
  255. state.error = e;
  256. }
  257. throw e;
  258. } finally {
  259. endFn(state);
  260. }
  261. }
  262. function __memoize(fn) {
  263. return function () {
  264. var args = Array.prototype.slice.call(arguments),
  265. hash = "",
  266. i = args.length,
  267. currentArg = null;
  268. while (i--) {
  269. currentArg = args[i];
  270. hash += (currentArg === Object(currentArg)) ? JSON.stringify(currentArg) : currentArg;
  271. fn.memoize || (fn.memoize = {});
  272. }
  273. return (hash in fn.memoize) ?
  274. fn.memoize[hash] :
  275. fn.memoize[hash] = fn.apply(this, args);
  276. };
  277. }
  278. function __getUuid() {
  279. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
  280. var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
  281. return v.toString(16);
  282. });
  283. }
  284. function __durationToSeconds(duration) {
  285. // basic algorithm from https://github.com/nezasa/iso8601-js-period
  286. if (typeof duration !== "string") throw new Error("Invalid ISO8601 duration '" + duration + "'");
  287. // regex splits as follows - grp0, grp1, y, m, d, grp2, h, m, s
  288. // 0 1 2 3 4 5 6 7 8
  289. var struct = /^P((\d+Y)?(\d+M)?(\d+D)?)?(T(\d+H)?(\d+M)?(\d+S)?)?$/.exec(duration);
  290. if (!struct) throw new Error("Invalid ISO8601 duration '" + duration + "'");
  291. var ymdhmsIndexes = [2, 3, 4, 6, 7, 8]; // -> grp1,y,m,d,grp2,h,m,s
  292. var factors = [31104000, // year (360*24*60*60)
  293. 2592000, // month (30*24*60*60)
  294. 86400, // day (24*60*60)
  295. 3600, // hour (60*60)
  296. 60, // minute (60)
  297. 1]; // second (1)
  298. var seconds = 0;
  299. for (var i = 0; i < 6; i++) {
  300. var digit = struct[ymdhmsIndexes[i]];
  301. // remove letters, replace by 0 if not defined
  302. digit = digit ? +digit.replace(/[A-Za-z]+/g, '') : 0;
  303. seconds += digit * factors[i];
  304. };
  305. return seconds;
  306. };
  307. // is functions
  308. function __classof(o) {
  309. if (o === null) {
  310. return "null";
  311. }
  312. if (o === undefined) {
  313. return "undefined";
  314. }
  315. return Object.prototype.toString.call(o).slice(8, -1).toLowerCase();
  316. }
  317. function __isDate(o) {
  318. return __classof(o) === "date" && !isNaN(o.getTime());
  319. }
  320. function __isFunction(o) {
  321. return __classof(o) === "function";
  322. }
  323. function __isGuid(value) {
  324. return (typeof value === "string") && /[a-fA-F\d]{8}-(?:[a-fA-F\d]{4}-){3}[a-fA-F\d]{12}/.test(value);
  325. }
  326. function __isDuration(value) {
  327. return (typeof value === "string") && /^(-|)?P([0-9]+Y|)?([0-9]+M|)?([0-9]+D|)?T?([0-9]+H|)?([0-9]+M|)?([0-9]+S|)?/.test(value);
  328. }
  329. function __isEmpty(obj) {
  330. if (obj === null || obj === undefined) {
  331. return true;
  332. }
  333. for (var key in obj) {
  334. if (__hasOwnProperty.call(obj, key)) {
  335. return false;
  336. }
  337. }
  338. return true;
  339. }
  340. function __isNumeric(n) {
  341. return !isNaN(parseFloat(n)) && isFinite(n);
  342. }
  343. // end of is Functions
  344. // string functions
  345. function __stringStartsWith(str, prefix) {
  346. // returns false for empty strings too
  347. if ((!str) || !prefix) return false;
  348. return str.indexOf(prefix, 0) === 0;
  349. }
  350. function __stringEndsWith(str, suffix) {
  351. // returns false for empty strings too
  352. if ((!str) || !suffix) return false;
  353. return str.indexOf(suffix, str.length - suffix.length) !== -1;
  354. }
  355. // Based on fragment from Dean Edwards' Base 2 library
  356. // format("a %1 and a %2", "cat", "dog") -> "a cat and a dog"
  357. function __formatString(string) {
  358. var args = arguments;
  359. var pattern = RegExp("%([1-" + (arguments.length - 1) + "])", "g");
  360. return string.replace(pattern, function (match, index) {
  361. return args[index];
  362. });
  363. };
  364. // end of string functions
  365. // shims
  366. if (!Object.create) {
  367. Object.create = function (parent) {
  368. var F = function () { };
  369. F.prototype = parent;
  370. return new F();
  371. };
  372. }
  373. var core = {};
  374. core.getOwnPropertyValues = __getOwnPropertyValues;
  375. core.objectForEach= __objectForEach;
  376. core.objectMapToArray= __objectMapToArray;
  377. core.objectFirst= __objectFirst;
  378. core.extend = __extend;
  379. core.propEq = __propEq;
  380. core.pluck = __pluck;
  381. core.arrayEquals = __arrayEquals;
  382. // core.arrayDistint = __arrayDistinct;
  383. core.arrayFirst = __arrayFirst;
  384. core.arrayIndexOf = __arrayIndexOf;
  385. core.arrayRemoveItem = __arrayRemoveItem;
  386. core.arrayZip = __arrayZip;
  387. core.requireLib = __requireLib;
  388. core.using = __using;
  389. core.wrapExecution = __wrapExecution;
  390. core.memoize = __memoize;
  391. core.getUuid = __getUuid;
  392. core.durationToSeconds = __durationToSeconds;
  393. core.isDate = __isDate;
  394. core.isGuid = __isGuid;
  395. core.isDuration = __isDuration;
  396. core.isFunction= __isFunction;
  397. core.isEmpty= __isEmpty;
  398. core.isNumeric= __isNumeric;
  399. core.stringStartsWith= __stringStartsWith;
  400. core.stringEndsWith= __stringEndsWith;
  401. core.formatString = __formatString;
  402. core.parent = breeze;
  403. breeze.core = core;
  404. /**
  405. @module core
  406. **/
  407. var Param = function () {
  408. // The %1 parameter
  409. // is required
  410. // must be a %2
  411. // must be an instance of %2
  412. // must be an instance of the %2 enumeration
  413. // must have a %2 property
  414. // must be an array where each element
  415. // is optional or
  416. var ctor = function(v, name) {
  417. this.v = v;
  418. this.name = name;
  419. this._contexts = [null];
  420. };
  421. var proto = ctor.prototype;
  422. proto.isObject = function() {
  423. return this.isTypeOf("object");
  424. };
  425. proto.isBoolean = function() {
  426. return this.isTypeOf('boolean');
  427. };
  428. proto.isString = function() {
  429. return this.isTypeOf('string');
  430. };
  431. proto.isNonEmptyString = function() {
  432. return addContext(this, {
  433. fn: isNonEmptyString,
  434. msg: "must be a nonEmpty string"
  435. });
  436. };
  437. function isNonEmptyString(context, v) {
  438. if (v == null) return false;
  439. return (typeof(v) === 'string') && v.length > 0;
  440. }
  441. proto.isNumber = function() {
  442. return this.isTypeOf('number');
  443. };
  444. proto.isFunction = function() {
  445. return this.isTypeOf('function');
  446. };
  447. proto.isTypeOf = function(typeName) {
  448. return addContext(this, {
  449. fn: isTypeOf,
  450. typeName: typeName,
  451. msg: __formatString("must be a '%1'", typeName)
  452. });
  453. };
  454. function isTypeOf(context, v) {
  455. if (v == null) return false;
  456. if (typeof(v) === context.typeName) return true;
  457. return false;
  458. }
  459. proto.isInstanceOf = function(type, typeName) {
  460. return addContext(this, {
  461. fn: isInstanceOf,
  462. type: type,
  463. typeName: typeName || type.prototype._$typeName,
  464. msg: __formatString("must be an instance of '%1'", typeName)
  465. });
  466. };
  467. function isInstanceOf(context, v) {
  468. if (v == null) return false;
  469. return (v instanceof context.type);
  470. }
  471. proto.hasProperty = function(propertyName) {
  472. return addContext(this, {
  473. fn: hasProperty,
  474. propertyName: propertyName,
  475. msg: __formatString("must have a '%1' property ", propertyName)
  476. });
  477. };
  478. function hasProperty(context, v) {
  479. if (v == null) return false;
  480. return (v[context.propertyName] !== undefined);
  481. }
  482. proto.isEnumOf = function(enumType) {
  483. return addContext(this, {
  484. fn: isEnumOf,
  485. enumType: enumType,
  486. msg: __formatString("must be an instance of the '%1' enumeration", enumType.name)
  487. });
  488. };
  489. function isEnumOf(context, v) {
  490. if (v == null) return false;
  491. return context.enumType.contains(v);
  492. }
  493. proto.isRequired = function(allowNull) {
  494. return addContext(this, {
  495. fn: isRequired,
  496. allowNull: allowNull,
  497. msg: "is required"
  498. });
  499. };
  500. function isRequired(context, v) {
  501. if (context.allowNull) {
  502. return v !== undefined;
  503. } else {
  504. return v != null;
  505. }
  506. }
  507. // combinable methods.
  508. proto.isOptional = function() {
  509. var context = {
  510. fn: isOptional,
  511. prevContext: null,
  512. msg: isOptionalMessage
  513. };
  514. return addContext(this, context);
  515. };
  516. function isOptional(context, v) {
  517. if (v == null) return true;
  518. var prevContext = context.prevContext;
  519. if (prevContext) {
  520. return prevContext.fn(prevContext, v);
  521. } else {
  522. return true;
  523. }
  524. }
  525. function isOptionalMessage(context, v) {
  526. var prevContext = context.prevContext;
  527. var element = prevContext ? " or it " + getMessage(prevContext, v) : "";
  528. return "is optional" + element;
  529. }
  530. proto.isNonEmptyArray = function() {
  531. return this.isArray(true);
  532. };
  533. proto.isArray = function(mustNotBeEmpty) {
  534. var context = {
  535. fn: isArray,
  536. mustNotBeEmpty: mustNotBeEmpty,
  537. prevContext: null,
  538. msg: isArrayMessage
  539. };
  540. return addContext(this, context);
  541. };
  542. function isArray(context, v) {
  543. if (!Array.isArray(v)) {
  544. return false;
  545. }
  546. if (context.mustNotBeEmpty) {
  547. if (v.length === 0) return false;
  548. }
  549. // allow standalone is array call.
  550. var prevContext = context.prevContext;
  551. if (!prevContext) return true;
  552. return v.every(function(v1) {
  553. return prevContext.fn(prevContext, v1);
  554. });
  555. }
  556. function isArrayMessage(context, v) {
  557. var arrayDescr = context.mustNotBeEmpty ? "a nonEmpty array" : "an array";
  558. var prevContext = context.prevContext;
  559. var element = prevContext ? " where each element " + getMessage(prevContext, v) : "";
  560. return " must be " + arrayDescr + element;
  561. }
  562. function getMessage(context, v) {
  563. var msg = context.msg;
  564. if (typeof(msg) === "function") {
  565. msg = msg(context, v);
  566. }
  567. return msg;
  568. }
  569. proto.or = function() {
  570. this._contexts.push(null);
  571. this._context = null;
  572. return this;
  573. };
  574. proto.check = function(defaultValue) {
  575. var ok = exec(this);
  576. if (ok === undefined) return;
  577. if (!ok) {
  578. throw new Error(this.getMessage());
  579. }
  580. if (this.v !== undefined) {
  581. return this.v;
  582. } else {
  583. return defaultValue;
  584. }
  585. };
  586. // called from outside this file.
  587. proto._addContext = function(context) {
  588. return addContext(this, context);
  589. };
  590. function addContext(that, context) {
  591. if (that._context) {
  592. var curContext = that._context;
  593. while (curContext.prevContext != null) {
  594. curContext = curContext.prevContext;
  595. }
  596. if (curContext.prevContext === null) {
  597. curContext.prevContext = context;
  598. // just update the prevContext but don't change the curContext.
  599. return that;
  600. } else if (context.prevContext === null) {
  601. context.prevContext = that._context;
  602. } else {
  603. throw new Error("Illegal construction - use 'or' to combine checks");
  604. }
  605. }
  606. return setContext(that, context);
  607. }
  608. function setContext(that, context) {
  609. that._contexts[that._contexts.length - 1] = context;
  610. that._context = context;
  611. return that;
  612. }
  613. function exec(self) {
  614. // clear off last one if null
  615. var contexts = self._contexts;
  616. if (contexts[contexts.length - 1] == null) {
  617. contexts.pop();
  618. }
  619. if (contexts.length === 0) {
  620. return undefined;
  621. }
  622. return contexts.some(function(context) {
  623. return context.fn(context, self.v);
  624. });
  625. }
  626. proto.getMessage = function() {
  627. var that = this;
  628. var message = this._contexts.map(function(context) {
  629. return getMessage(context, that.v);
  630. }).join(", or it ");
  631. return __formatString(this.MESSAGE_PREFIX, this.name) + " " + message;
  632. };
  633. proto.withDefault = function(defaultValue) {
  634. this.defaultValue = defaultValue;
  635. return this;
  636. };
  637. proto.whereParam = function(propName) {
  638. return this.parent.whereParam(propName);
  639. };
  640. proto.applyAll = function(instance, throwIfUnknownProperty) {
  641. throwIfUnknownProperty = throwIfUnknownProperty == null ? true : throwIfUnknownProperty;
  642. var clone = __extend({}, this.parent.config);
  643. this.parent.params.forEach(function(p) {
  644. if (throwIfUnknownProperty) delete clone[p.name];
  645. p._applyOne(instance);
  646. });
  647. // should be no properties left in the clone
  648. if (throwIfUnknownProperty) {
  649. for (var key in clone) {
  650. // allow props with an undefined value
  651. if (clone[key] !== undefined) {
  652. throw new Error("Invalid property in config: " + key);
  653. }
  654. }
  655. }
  656. };
  657. proto._applyOne = function(instance) {
  658. this.check();
  659. if (this.v !== undefined) {
  660. instance[this.name] = this.v;
  661. } else {
  662. if (this.defaultValue !== undefined) {
  663. instance[this.name] = this.defaultValue;
  664. }
  665. }
  666. };
  667. proto.MESSAGE_PREFIX = "The '%1' parameter ";
  668. return ctor;
  669. }();
  670. var assertParam = function (v, name) {
  671. return new Param(v, name);
  672. };
  673. var ConfigParam = function() {
  674. var ctor = function(config) {
  675. if (typeof(config) !== "object") {
  676. throw new Error("Configuration parameter should be an object, instead it is a: " + typeof(config));
  677. }
  678. this.config = config;
  679. this.params = [];
  680. };
  681. var proto = ctor.prototype;
  682. proto.whereParam = function(propName) {
  683. var param = new Param(this.config[propName], propName);
  684. param.parent = this;
  685. this.params.push(param);
  686. return param;
  687. };
  688. return ctor;
  689. }();
  690. var assertConfig = function(config) {
  691. return new ConfigParam(config);
  692. };
  693. // Param is exposed so that additional 'is' methods can be added to the prototype.
  694. core.Param = Param;
  695. core.assertParam = assertParam;
  696. core.assertConfig = assertConfig;
  697. /**
  698. @module core
  699. **/
  700. var Enum = function() {
  701. // TODO: think about CompositeEnum (flags impl).
  702. /**
  703. Base class for all Breeze enumerations, such as EntityState, DataType, FetchStrategy, MergeStrategy etc.
  704. A Breeze Enum is a namespaced set of constant values. Each Enum consists of a group of related constants, called 'symbols'.
  705. Unlike enums in some other environments, each 'symbol' can have both methods and properties.
  706. See the example below:
  707. // Example of creating a new Enum
  708. var prototype = {
  709. nextDay: function () {
  710. var nextIndex = (this.dayIndex+1) % 7;
  711. return DayOfWeek.getSymbols()[nextIndex];
  712. }
  713. };
  714. var DayOfWeek = new Enum("DayOfWeek", prototype);
  715. DayOfWeek.Monday = DayOfWeek.addSymbol( { dayIndex: 0 });
  716. DayOfWeek.Tuesday = DayOfWeek.addSymbol( { dayIndex: 1 });
  717. DayOfWeek.Wednesday = DayOfWeek.addSymbol( { dayIndex: 2 });
  718. DayOfWeek.Thursday = DayOfWeek.addSymbol( { dayIndex: 3 });
  719. DayOfWeek.Friday = DayOfWeek.addSymbol( { dayIndex: 4 });
  720. DayOfWeek.Saturday = DayOfWeek.addSymbol( { dayIndex: 5, isWeekend: true });
  721. DayOfWeek.Sunday = DayOfWeek.addSymbol( { dayIndex: 6, isWeekend: true });
  722. DayOfWeek.seal();
  723. // custom methods
  724. ok(DayOfWeek.Monday.nextDay() === DayOfWeek.Tuesday);
  725. ok(DayOfWeek.Sunday.nextDay() === DayOfWeek.Monday);
  726. // custom properties
  727. ok(DayOfWeek.Tuesday.isWeekend === undefined);
  728. ok(DayOfWeek.Saturday.isWeekend == true);
  729. // Standard enum capabilities
  730. ok(DayOfWeek instanceof Enum);
  731. ok(Enum.isSymbol(DayOfWeek.Wednesday));
  732. ok(DayOfWeek.contains(DayOfWeek.Thursday));
  733. ok(DayOfWeek.Tuesday.parentEnum == DayOfWeek);
  734. ok(DayOfWeek.getSymbols().length === 7);
  735. ok(DayOfWeek.Friday.toString() === "Friday");
  736. @class Enum
  737. **/
  738. /**
  739. Enum constructor - may be used to create new Enums.
  740. @example
  741. var prototype = {
  742. nextDay: function () {
  743. var nextIndex = (this.dayIndex+1) % 7;
  744. return DayOfWeek.getSymbols()[nextIndex];
  745. }
  746. };
  747. var DayOfWeek = new Enum("DayOfWeek", prototype);
  748. @method <ctor> Enum
  749. @param name {String}
  750. @param [methodObj] {Object}
  751. **/
  752. var ctor = function(name, methodObj) {
  753. this.name = name;
  754. var prototype = new EnumSymbol(methodObj);
  755. prototype.parentEnum = this;
  756. this._symbolPrototype = prototype;
  757. if (methodObj) {
  758. Object.keys(methodObj).forEach(function(key) {
  759. prototype[key] = methodObj[key];
  760. });
  761. }
  762. };
  763. var proto = ctor.prototype;
  764. /**
  765. Checks if an object is an Enum 'symbol'.
  766. @example
  767. if (Enum.isSymbol(DayOfWeek.Wednesday)) {
  768. // do something ...
  769. };
  770. @method isSymbol
  771. @return {Boolean}
  772. @static
  773. **/
  774. ctor.isSymbol = function(obj) {
  775. return obj instanceof EnumSymbol;
  776. };
  777. /**
  778. Returns an Enum symbol given its name.
  779. @example
  780. var dayOfWeek = DayOfWeek.from("Thursday");
  781. // nowdayOfWeek === DayOfWeek.Thursday
  782. @method fromName
  783. @param name {String} Name for which an enum symbol should be returned.
  784. @return {EnumSymbol} The symbol that matches the name or 'undefined' if not found.
  785. **/
  786. proto.fromName = function(name) {
  787. return this[name];
  788. };
  789. /**
  790. Adds a new symbol to an Enum.
  791. @example
  792. var DayOfWeek = new Enum("DayOfWeek", prototype);
  793. DayOfWeek.Monday = DayOfWeek.addSymbol( { dayIndex: 0 });
  794. @method addSymbol
  795. @param [propertiesObj] {Object} A collection of properties that should be added to the new symbol.
  796. In other words, the 'propertiesObj' is any state that should be held by the symbol.
  797. @return {EnumSymbol} The new symbol
  798. **/
  799. proto.addSymbol = function(propertiesObj) {
  800. // TODO: check if sealed.
  801. var newSymbol = Object.create(this._symbolPrototype);
  802. if (propertiesObj) {
  803. Object.keys(propertiesObj).forEach(function(key) {
  804. newSymbol[key] = propertiesObj[key];
  805. });
  806. }
  807. setTimeout(function() { newSymbol.getName(); }, 0);
  808. return newSymbol;
  809. };
  810. /**
  811. Seals this enum so that no more symbols may be added to it. This should only be called after all symbols
  812. have already been added to the Enum.
  813. @example
  814. DayOfWeek.seal();
  815. @method seal
  816. **/
  817. proto.seal = function() {
  818. this.getSymbols().forEach(function(sym) { return sym.getName(); });
  819. };
  820. //// TODO: remove or rethink this.
  821. //Enum.prototype.combineSymbols = function () {
  822. // var proto = this._symbolPrototype;
  823. // var newSymbol = Object.create(proto);
  824. // newSymbol._symbols = Array.prototype.slice.call(arguments);
  825. // Object.keys(proto).forEach(function (key) {
  826. // var result;
  827. // var oldMethod = proto[key];
  828. // if (__isFunction(oldMethod)) {
  829. // var newMethod = function () {
  830. // if (this._symbols) {
  831. // result = this._symbols.map(function (sym) {
  832. // return oldMethod.apply(sym);
  833. // });
  834. // } else {
  835. // result = oldMethod.apply(this);
  836. // }
  837. // return result;
  838. // };
  839. // proto[key] = newMethod;
  840. // }
  841. // });
  842. // return newSymbol;
  843. //};
  844. /**
  845. Returns all of the symbols contained within this Enum.
  846. @example
  847. var symbols = DayOfWeek.getSymbols();
  848. @method getSymbols
  849. @return {Array of EnumSymbol} All of the symbols contained within this Enum.
  850. **/
  851. proto.getSymbols = function() {
  852. return this.getNames().map(function(key) {
  853. return this[key];
  854. }, this);
  855. };
  856. /**
  857. Returns the names of all of the symbols contained within this Enum.
  858. @example
  859. var symbols = DayOfWeek.getNames();
  860. @method getNames
  861. @return {Array of String} All of the names of the symbols contained within this Enum.
  862. **/
  863. proto.getNames = function() {
  864. var result = [];
  865. for (var key in this) {
  866. if (this.hasOwnProperty(key)) {
  867. if (key != "name" && key.substr(0, 1) !== "_" && !__isFunction(this[key])) {
  868. result.push(key);
  869. }
  870. }
  871. }
  872. return result;
  873. };
  874. /**
  875. Returns whether an Enum contains a specified symbol.
  876. @example
  877. var symbol = DayOfWeek.Friday;
  878. if (DayOfWeek.contains(symbol)) {
  879. // do something
  880. }
  881. @method contains
  882. @param {Object} Object or symbol to test.
  883. @return {Boolean} Whether this Enum contains the specified symbol.
  884. **/
  885. proto.contains = function(sym) {
  886. if (!(sym instanceof EnumSymbol)) {
  887. return false;
  888. }
  889. return this[sym.getName()] === sym;
  890. };
  891. /**
  892. One of the constant values that is generated by the {{#crossLink "Enum"}}{{/crossLink}} "addSymbol" method. EnumSymbols should ONLY be created via
  893. the Enum.addSymbol method.
  894. var DayOfWeek = new Enum("DayOfWeek");
  895. DayOfWeek.Monday = DayOfWeek.addSymbol();
  896. @class EnumSymbol
  897. **/
  898. function EnumSymbol() {
  899. }
  900. /**
  901. The {{#crossLink "Enum"}}{{/crossLink}} to which this symbol belongs.
  902. __readOnly__
  903. @property parentEnum {Enum}
  904. **/
  905. /**
  906. Returns the name of this symbol.
  907. @example
  908. var name = DayOfWeek.Monday.getName();
  909. // name === "Monday"
  910. @method getName
  911. **/
  912. EnumSymbol.prototype.getName = function() {
  913. if (!this.name) {
  914. var that = this;
  915. this.name = __arrayFirst(this.parentEnum.getNames(), function(name) {
  916. return that.parentEnum[name] === that;
  917. });
  918. }
  919. return this.name;
  920. };
  921. /**
  922. Same as the getName method. Returns the name of this symbol.
  923. @example
  924. var name = DayOfWeek.Monday.toString();
  925. // name === "Monday"
  926. @method toString
  927. **/
  928. EnumSymbol.prototype.toString = function() {
  929. return this.getName();
  930. };
  931. EnumSymbol.prototype.toJSON = function() {
  932. return {
  933. _$typeName: this.parentEnum.name,
  934. name: this.name
  935. };
  936. };
  937. return ctor;
  938. }();
  939. core.Enum = Enum;
  940. /**
  941. @module core
  942. **/
  943. var Event = function() {
  944. var __eventNameMap = {};
  945. /**
  946. Class to support basic event publication and subscription semantics.
  947. @class Event
  948. **/
  949. /**
  950. Constructor for an Event
  951. @example
  952. salaryEvent = new Event("salaryEvent", person);
  953. @method <ctor> Event
  954. @param name {String}
  955. @param publisher {Object} The object that will be doing the publication. i.e. the object to which this event is attached.
  956. @param [defaultErrorCallback] {Function} If omitted then subscriber notification failures will be ignored.
  957. errorCallback([e])
  958. @param [defaultErrorCallback.e] {Error} Any error encountered during subscription execution.
  959. **/
  960. var ctor = function(name, publisher, defaultErrorCallback) {
  961. assertParam(name, "eventName").isNonEmptyString().check();
  962. assertParam(publisher, "publisher").isObject().check();
  963. this.name = name;
  964. // register the name
  965. __eventNameMap[name] = true;
  966. this.publisher = publisher;
  967. this._nextUnsubKey = 1;
  968. if (defaultErrorCallback) {
  969. this._defaultErrorCallback = defaultErrorCallback;
  970. }
  971. };
  972. var proto = ctor.prototype;
  973. /**
  974. Publish data for this event.
  975. @example
  976. // Assume 'salaryEvent' is previously constructed Event
  977. salaryEvent.publish( { eventType: "payRaise", amount: 100 });
  978. This event can also be published asychonously
  979. @example
  980. salaryEvent.publish( { eventType: "payRaise", amount: 100 }, true);
  981. And we can add a handler in case the subscriber 'mishandles' the event.
  982. @example
  983. salaryEvent.publish( { eventType: "payRaise", amount: 100 }, true, function(error) {
  984. // do something with the 'error' object
  985. });
  986. @method publish
  987. @param data {Object} Data to publish
  988. @param [publishAsync=false] Whether to publish asynchonously or not.
  989. @param [errorCallback] {Function} Will be called for any errors that occur during publication. If omitted,
  990. errors will be eaten.
  991. errorCallback([e])
  992. @param [errorCallback.e] {Error} Any error encountered during publication execution.
  993. @return {Boolean} false if event is disabled; true otherwise.
  994. **/
  995. proto.publish = function(data, publishAsync, errorCallback) {
  996. if (!ctor._isEnabled(this.name, this.publisher)) return false;
  997. if (publishAsync === true) {
  998. setTimeout(publishCore, 0, this, data, errorCallback);
  999. } else {
  1000. publishCore(this, data, errorCallback);
  1001. }
  1002. return true;
  1003. };
  1004. function publishCore(that, data, errorCallback) {
  1005. var subscribers = that._subscribers;
  1006. if (!subscribers) return true;
  1007. // subscribers from outer scope.
  1008. subscribers.forEach(function(s) {
  1009. try {
  1010. s.callback(data);
  1011. } catch(e) {
  1012. e.context = "unable to publish on topic: " + that.name;
  1013. if (errorCallback) {
  1014. errorCallback(e);
  1015. } else if (that._defaultErrorCallback) {
  1016. that._defaultErrorCallback(e);
  1017. } else {
  1018. fallbackErrorHandler(e);
  1019. }
  1020. }
  1021. });
  1022. }
  1023. /**
  1024. Publish data for this event asynchronously.
  1025. @example
  1026. // Assume 'salaryEvent' is previously constructed Event
  1027. salaryEvent.publishAsync( { eventType: "payRaise", amount: 100 });
  1028. And we can add a handler in case the subscriber 'mishandles' the event.
  1029. @example
  1030. salaryEvent.publishAsync( { eventType: "payRaise", amount: 100 }, function(error) {
  1031. // do something with the 'error' object
  1032. });
  1033. @method publishAsync
  1034. @param data {Object} Data to publish
  1035. @param [errorCallback] {Function} Will be called for any errors that occur during publication. If omitted,
  1036. errors will be eaten.
  1037. errorCallback([e])
  1038. @param [errorCallback.e] {Error} Any error encountered during publication execution.
  1039. **/
  1040. proto.publishAsync = function(data, errorCallback) {
  1041. this.publish(data, true, errorCallback);
  1042. };
  1043. /**
  1044. Subscribe to this event.
  1045. @example
  1046. // Assume 'salaryEvent' is previously constructed Event
  1047. salaryEvent.subscribe(function (eventArgs) {
  1048. if (eventArgs.eventType === "payRaise") {
  1049. // do something
  1050. }
  1051. });
  1052. There are several built in Breeze events, such as EntityAspect.propertyChanged, EntityAspect.validationErrorsChanged as well.
  1053. @example
  1054. // Assume order is a preexisting 'order' entity
  1055. order.entityAspect.propertyChanged.subscribe(function (pcEvent) {
  1056. if ( pcEvent.propertyName === "OrderDate") {
  1057. // do something
  1058. }
  1059. });
  1060. @method subscribe
  1061. @param [callback] {Function} Will be called whenever 'data' is published for this event.
  1062. callback([data])
  1063. @param [callback.data] {Object} Whatever 'data' was published. This should be documented on the specific event.
  1064. @return {Number} This is a key for 'unsubscription'. It can be passed to the 'unsubscribe' method.
  1065. **/
  1066. proto.subscribe = function(callback) {
  1067. if (!this._subscribers) {
  1068. this._subscribers = [];
  1069. }
  1070. var unsubKey = this._nextUnsubKey;
  1071. this._subscribers.push({ unsubKey: unsubKey, callback: callback });
  1072. ++this._nextUnsubKey;
  1073. return unsubKey;
  1074. };
  1075. /**
  1076. Unsubscribe from this event.
  1077. @example
  1078. // Assume order is a preexisting 'order' entity
  1079. var token = order.entityAspect.propertyChanged.subscribe(function (pcEvent) {
  1080. // do something
  1081. });
  1082. // sometime later
  1083. order.entityAspect.propertyChanged.unsubscribe(token);
  1084. @method unsubscribe
  1085. @param unsubKey {Number} The value returned from the 'subscribe' method may be used to unsubscribe here.
  1086. @return {Boolean} Whether unsubscription occured. This will return false if already unsubscribed or if the key simply
  1087. cannot be found.
  1088. **/
  1089. proto.unsubscribe = function(unsubKey) {
  1090. if (!this._subscribers) return false;
  1091. var subs = this._subscribers;
  1092. var ix = __arrayIndexOf(subs, function(s) {
  1093. return s.unsubKey === unsubKey;
  1094. });
  1095. if (ix !== -1) {
  1096. subs.splice(ix, 1);
  1097. if (subs.length === 0) {
  1098. this._subscribers = null;
  1099. }
  1100. return true;
  1101. } else {
  1102. return false;
  1103. }
  1104. };
  1105. proto.clear = function() {
  1106. this._subscribers = null;
  1107. };
  1108. // event bubbling - document later.
  1109. ctor.bubbleEvent = function(target, getParentFn) {
  1110. target._getEventParent = getParentFn;
  1111. };
  1112. /**
  1113. Enables or disables the named event for an object and all of its children.
  1114. @example
  1115. Event.enable(“propertyChanged”, myEntityManager, false)
  1116. will disable all EntityAspect.propertyChanged events within a EntityManager.
  1117. @example
  1118. Event.enable(“propertyChanged”, myEntityManager, true)
  1119. will enable all EntityAspect.propertyChanged events within a EntityManager.
  1120. @example
  1121. Event.enable(“propertyChanged”, myEntity.entityAspect, false)
  1122. will disable EntityAspect.propertyChanged events for a specific entity.
  1123. @example
  1124. Event.enable(“propertyChanged”, myEntity.entityAspect, null)
  1125. will removes any enabling / disabling at the entity aspect level so now any 'Event.enable' calls at the EntityManager level,
  1126. made either previously or in the future, will control notification.
  1127. @example
  1128. Event.enable(“validationErrorsChanged”, myEntityManager, function(em) {
  1129. return em.customTag === “blue”;
  1130. })
  1131. will either enable or disable myEntityManager based on the current value of a ‘customTag’ property on myEntityManager.
  1132. Note that this is dynamic, changing the customTag value will cause events to be enabled or disabled immediately.
  1133. @method enable
  1134. @static
  1135. @param eventName {String} The name of the event.
  1136. @param target {Object} The object at which enabling or disabling will occur. All event notifications that occur to this object or
  1137. children of this object will be enabled or disabled.
  1138. @param isEnabled {Boolean|null|Function} A boolean, a null or a function that returns either a boolean or a null.
  1139. **/
  1140. ctor.enable = function(eventName, obj, isEnabled) {
  1141. assertParam(eventName, "eventName").isNonEmptyString().check();
  1142. assertParam(obj, "obj").isObject().check();
  1143. assertParam(isEnabled, "isEnabled").isBoolean().isOptional().or().isFunction().check();
  1144. eventName = getFullEventName(eventName);
  1145. if (!obj._$eventMap) {
  1146. obj._$eventMap = {};
  1147. }
  1148. obj._$eventMap[eventName] = isEnabled;
  1149. };
  1150. ctor._enableFast = function(event, obj, isEnabled) {
  1151. if (!obj._$eventMap) {
  1152. obj._$eventMap = {};
  1153. }
  1154. obj._$eventMap[event.name] = isEnabled;
  1155. };
  1156. /**
  1157. Returns whether for a specific event and a specific object and its children, notification is enabled or disabled or not set.
  1158. @example
  1159. Event.isEnabled(“propertyChanged”, myEntityManager)
  1160. @method isEnabled
  1161. @static
  1162. @param eventName {String} The name of the event.
  1163. @param target {Object} The object for which we want to know if notifications are enabled.
  1164. @return {Boolean|null} A null is returned if this value has not been set.
  1165. **/
  1166. ctor.isEnabled = function(eventName, obj) {
  1167. assertParam(eventName, "eventName").isNonEmptyString().check();
  1168. assertParam(obj, "obj").isObject().check();
  1169. if (!obj._getEventParent) {
  1170. throw new Error("This object does not support event enabling/disabling");
  1171. }
  1172. return ctor._isEnabled(obj, getFullEventName(eventName));
  1173. };
  1174. ctor._isEnabled = function(eventName, obj) {
  1175. var isEnabled = null;
  1176. var eventMap = obj._$eventMap;
  1177. if (eventMap) {
  1178. isEnabled = eventMap[eventName];
  1179. }
  1180. if (isEnabled != null) {
  1181. if (typeof isEnabled === 'function') {
  1182. return isEnabled(obj);
  1183. } else {
  1184. return !!isEnabled;
  1185. }
  1186. } else {
  1187. var parent = obj._getEventParent && obj._getEventParent();
  1188. if (parent) {
  1189. return ctor._isEnabled(eventName, parent);
  1190. } else {
  1191. // default if not explicitly disabled.
  1192. return true;
  1193. }
  1194. }
  1195. };
  1196. function getFullEventName(eventName) {
  1197. if (__eventNameMap[eventName]) return eventName;
  1198. // find the closest event name that matches
  1199. var fullEventName = __arrayFirst(Object.keys(__eventNameMap), function(name) {
  1200. return name.indexOf(eventName) === 0;
  1201. });
  1202. if (!fullEventName) {
  1203. throw new Error("Unable to find any registered event that matches: " + eventName);
  1204. }
  1205. return fullEventName;
  1206. }
  1207. function fallbackErrorHandler(e) {
  1208. // TODO: maybe log this
  1209. // for now do nothing;
  1210. }
  1211. return ctor;
  1212. }();
  1213. core.Event = Event;
  1214. /**
  1215. @module breeze
  1216. **/
  1217. var __config = function () {
  1218. // alias for within fns with a config param
  1219. var __config = {};
  1220. __config.functionRegistry = {};
  1221. __config.typeRegistry = {};
  1222. __config.objectRegistry = {};
  1223. __config.interfaceInitialized = new Event("interfaceInitialized_config", __config);
  1224. var InterfaceDef = function(name) {
  1225. this.name = name;
  1226. this.defaultInstance = null;
  1227. this._implMap = {};
  1228. };
  1229. InterfaceDef.prototype.registerCtor = function(adapterName, ctor) {
  1230. this._implMap[adapterName.toLowerCase()] = { ctor: ctor, defaultInstance: null };
  1231. };
  1232. InterfaceDef.prototype.getImpl = function(adapterName) {
  1233. return this._implMap[adapterName.toLowerCase()];
  1234. };
  1235. InterfaceDef.prototype.getFirstImpl = function() {
  1236. var kv = __objectFirst(this._implMap, function() { return true; });
  1237. return kv ? kv.value : null;
  1238. };
  1239. __config.interfaceRegistry = {
  1240. ajax: new InterfaceDef("ajax"),
  1241. modelLibrary: new InterfaceDef("modelLibrary"),
  1242. dataService: new InterfaceDef("dataService")
  1243. };
  1244. __config.interfaceRegistry.modelLibrary.getDefaultInstance = function() {
  1245. if (!this.defaultInstance) {
  1246. throw new Error("Unable to locate the default implementation of the '" + this.name
  1247. + "' interface. Possible options are 'ko', 'backingStore' or 'backbone'. See the breeze.config.initializeAdapterInstances method.");
  1248. }
  1249. return this.defaultInstance;
  1250. };
  1251. /**
  1252. A singleton object that is the repository of all configuration options.
  1253. config.initializeAdapterInstance( {
  1254. modelLibrary: "ko",
  1255. dataService: "webApi"
  1256. });
  1257. @class config
  1258. **/
  1259. /**
  1260. This method is now OBSOLETE. Use the "initializeAdapterInstances" to accomplish the same result.
  1261. @method setProperties
  1262. @deprecated
  1263. @param config {Object}
  1264. @param [config.remoteAccessImplementation] { implementation of remoteAccess-interface }
  1265. @param [config.trackingImplementation] { implementation of entityTracking-interface }
  1266. @param [config.ajaxImplementation] {implementation of ajax-interface }
  1267. **/
  1268. __config.setProperties = function(config) {
  1269. assertConfig(config)
  1270. .whereParam("remoteAccessImplementation").isOptional()
  1271. .whereParam("trackingImplementation").isOptional()
  1272. .whereParam("ajaxImplementation").isOptional()
  1273. .applyAll(config);
  1274. if (config.remoteAccessImplementation) {
  1275. __config.initializeAdapterInstance("dataService", config.remoteAccessImplementation);
  1276. }
  1277. if (config.trackingImplementation) {
  1278. // note the name change
  1279. __config.initializeAdapterInstance("modelLibrary", config.trackingImplementation);
  1280. }
  1281. if (config.ajaxImplementation) {
  1282. __config.initializeAdapterInstance("ajax", config.ajaxImplementation);
  1283. }
  1284. };
  1285. /**
  1286. Method use to register implementations of standard breeze interfaces. Calls to this method are usually
  1287. made as the last step within an adapter implementation.
  1288. @method registerAdapter
  1289. @param interfaceName {String} - one of the following interface names "ajax", "dataService" or "modelLibrary"
  1290. @param adapterCtor {Function} - an ctor function that returns an instance of the specified interface.
  1291. **/
  1292. __config.registerAdapter = function(interfaceName, adapterCtor) {
  1293. assertParam(interfaceName, "interfaceName").isNonEmptyString().check();
  1294. assertParam(adapterCtor, "adapterCtor").isFunction().check();
  1295. // this impl will be thrown away after the name is retrieved.
  1296. var impl = new adapterCtor();
  1297. var implName = impl.name;
  1298. if (!implName) {
  1299. throw new Error("Unable to locate a 'name' property on the constructor passed into the 'registerAdapter' call.");
  1300. }
  1301. var idef = getInterfaceDef(interfaceName);
  1302. idef.registerCtor(implName, adapterCtor);
  1303. };
  1304. /**
  1305. Returns the ctor function used to implement a specific interface with a specific adapter name.
  1306. @method getAdapter
  1307. @param interfaceName {String} One of the following interface names "ajax", "dataService" or "modelLibrary"
  1308. @param [adapterName] {String} The name of any previously registered adapter. If this parameter is omitted then
  1309. this method returns the "default" adapter for this interface. If there is no default adapter, then a null is returned.
  1310. @return {Function|null} Returns either a ctor function or null.
  1311. **/
  1312. __config.getAdapter = function(interfaceName, adapterName) {
  1313. var idef = getInterfaceDef(interfaceName);
  1314. if (adapterName) {
  1315. var impl = idef.getImpl(adapterName);
  1316. return impl ? impl.ctor : null;
  1317. } else {
  1318. return idef.defaultInstance ? idef.defaultInstance._$impl.ctor : null;
  1319. }
  1320. };
  1321. /**
  1322. Initializes a collection of adapter implementations and makes each one the default for its corresponding interface.
  1323. @method initializeAdapterInstances
  1324. @param config {Object}
  1325. @param [config.ajax] {String} - the name of a previously registered "ajax" adapter
  1326. @param [config.dataService] {String} - the name of a previously registered "dataService" adapter
  1327. @param [config.modelLibrary] {String} - the name of a previously registered "modelLibrary" adapter
  1328. @return [array of instances]
  1329. **/
  1330. __config.initializeAdapterInstances = function(config) {
  1331. assertConfig(config)
  1332. .whereParam("dataService").isOptional()
  1333. .whereParam("modelLibrary").isOptional()
  1334. .whereParam("ajax").isOptional();
  1335. return __objectMapToArray(config, __config.initializeAdapterInstance);
  1336. };
  1337. /**
  1338. Initializes a single adapter implementation. Initialization means either newing a instance of the
  1339. specified interface and then calling "initialize" on it or simply calling "initialize" on the instance
  1340. if it already exists.
  1341. @method initializeAdapterInstance
  1342. @param interfaceName {String} The name of the interface to which the adapter to initialize belongs.
  1343. @param adapterName {String} - The name of a previously registered adapter to initialize.
  1344. @param [isDefault=true] {Boolean} - Whether to make this the default "adapter" for this interface.
  1345. @return {an instance of the specified adapter}
  1346. **/
  1347. __config.initializeAdapterInstance = function(interfaceName, adapterName, isDefault) {
  1348. isDefault = isDefault === undefined ? true : isDefault;
  1349. assertParam(interfaceName, "interfaceName").isNonEmptyString().check();
  1350. assertParam(adapterName, "adapterName").isNonEmptyString().check();
  1351. assertParam(isDefault, "isDefault").isBoolean().check();
  1352. var idef = getInterfaceDef(interfaceName);
  1353. var impl = idef.getImpl(adapterName);
  1354. if (!impl) {
  1355. throw new Error("Unregistered adapter. Interface: " + interfaceName + " AdapterName: " + adapterName);
  1356. }
  1357. return initializeAdapterInstanceCore(idef, impl, isDefault);
  1358. };
  1359. /**
  1360. Returns the adapter instance corresponding to the specified interface and adapter names.
  1361. @method getAdapterInstance
  1362. @param interfaceName {String} The name of the interface.
  1363. @param [adapterName] {String} - The name of a previously registered adapter. If this parameter is
  1364. omitted then the default implementation of the specified interface is returned. If there is
  1365. no defaultInstance of this interface, then the first registered instance of this interface is returned.
  1366. @return {an instance of the specified adapter}
  1367. **/
  1368. __config.getAdapterInstance = function(interfaceName, adapterName) {
  1369. var idef = getInterfaceDef(interfaceName);
  1370. var impl;
  1371. if (adapterName & adapterName !== "") {
  1372. impl = idef.getImpl(adapterName);
  1373. return impl ? impl.defaultInstance : null;
  1374. } else {
  1375. if (idef.defaultInstance) {
  1376. return idef.defaultInstance;
  1377. } else {
  1378. impl = idef.getFirstImpl();
  1379. if (impl.defaultInstance) {
  1380. return impl.defaultInstance;
  1381. } else {
  1382. return initializeAdapterInstanceCore(idef, impl, true);
  1383. }
  1384. }
  1385. }
  1386. };
  1387. // this is needed for reflection purposes when deserializing an object that needs a fn or ctor
  1388. // used to register validators.
  1389. __config.registerFunction = function(fn, fnName) {
  1390. assertParam(fn, "fn").isFunction().check();
  1391. assertParam(fnName, "fnName").isString().check();
  1392. fn.prototype._$fnName = fnName;
  1393. __config.functionRegistry[fnName] = fn;
  1394. };
  1395. __config._storeObject = function(obj, type, name) {
  1396. // uncomment this if we make this public.
  1397. //assertParam(obj, "obj").isObject().check();
  1398. //assertParam(name, "objName").isString().check();
  1399. var key = (typeof(type) === "string" ? type : type.prototype._$typeName) + "." + name;
  1400. __config.objectRegistry[key] = obj;
  1401. };
  1402. __config._fetchObject = function(type, name) {
  1403. if (!name) return undefined;
  1404. var key = (typeof(type) === "string" ? type : type.prototype._$typeName) + "." + name;
  1405. var result = __config.objectRegistry[key];
  1406. if (!result) {
  1407. throw new Error("Unable to locate a registered object by the name: " + key);
  1408. }
  1409. return result;
  1410. };
  1411. __config.registerType = function(ctor, typeName) {
  1412. assertParam(ctor, "ctor").isFunction().check();
  1413. assertParam(typeName, "typeName").isString().check();
  1414. ctor.prototype._$typeName = typeName;
  1415. __config.typeRegistry[typeName] = ctor;
  1416. };
  1417. //a_config._registerNamedInstance = function (instance, nameProperty) {
  1418. // a_config.registerFunction(function () { return instance; }, instance._$typeName + "." + instance[nameProperty || "name"]);
  1419. //};
  1420. //a_config._namedInstanceFromJson = function (type, nameProperty, json) {
  1421. // var key = type.proto._$typeName + "." + json[nameProperty || "name"];
  1422. // var fn = a_config.functionRegistry[key];
  1423. // if (!fn) {
  1424. // throw new Error("Unable to locate " + key);
  1425. // }
  1426. // return fn(json);
  1427. //};
  1428. __config.stringifyPad = " ";
  1429. function initializeAdapterInstanceCore(interfaceDef, impl, isDefault) {
  1430. var instance = impl.defaultInstance;
  1431. if (!instance) {
  1432. instance = new (impl.ctor)();
  1433. impl.defaultInstance = instance;
  1434. instance._$impl = impl;
  1435. }
  1436. instance.initialize();
  1437. if (isDefault) {
  1438. // next line needs to occur before any recomposition
  1439. interfaceDef.defaultInstance = instance;
  1440. }
  1441. // recomposition of other impls will occur here.
  1442. __config.interfaceInitialized.publish({ interfaceName: interfaceDef.name, instance: instance, isDefault: true });
  1443. if (instance.checkForRecomposition) {
  1444. // now register for own dependencies.
  1445. __config.interfaceInitialized.subscribe(function(interfaceInitializedArgs) {
  1446. instance.checkForRecomposition(interfaceInitializedArgs);
  1447. });
  1448. }
  1449. return instance;
  1450. }
  1451. function getInterfaceDef(interfaceName) {
  1452. var lcName = interfaceName.toLowerCase();
  1453. // source may be null
  1454. var kv = __objectFirst(__config.interfaceRegistry || {}, function(k, v) {
  1455. return k.toLowerCase() === lcName;
  1456. });
  1457. if (!kv) {
  1458. throw new Error("Unknown interface name: " + interfaceName);
  1459. }
  1460. return kv.value;
  1461. }
  1462. return __config;
  1463. }();
  1464. var __modelLibraryDef = __config.interfaceRegistry.modelLibrary;
  1465. // legacy
  1466. core.config = __config;
  1467. breeze.config = __config;
  1468. /**
  1469. @module breeze
  1470. **/
  1471. var DataType = function () {
  1472. /**
  1473. DataType is an 'Enum' containing all of the supported data types.
  1474. @class DataType
  1475. @static
  1476. **/
  1477. /**
  1478. The default value of this DataType.
  1479. @property defaultValue {any}
  1480. **/
  1481. /**
  1482. Whether this is a 'numeric' DataType.
  1483. @property isNumeric {Boolean}
  1484. **/
  1485. var dataTypeMethods = {
  1486. // default
  1487. };
  1488. var coerceToString = function (source, sourceTypeName) {
  1489. return (source == null) ? source : source.toString();
  1490. };
  1491. var coerceToInt = function (source, sourceTypeName) {
  1492. if (sourceTypeName === "string") {
  1493. var val = parseInt(source, 10);
  1494. return isNaN(val) ? source : val;
  1495. } else if (sourceTypeName === "number") {
  1496. return Math.round(source);
  1497. }
  1498. // do we want to coerce floats -> ints
  1499. return source;
  1500. };
  1501. var coerceToFloat = function (source, sourceTypeName) {
  1502. if (sourceTypeName === "string") {
  1503. var val = parseFloat(source);
  1504. return isNaN(val) ? source : val;
  1505. }
  1506. return source;
  1507. };
  1508. var coerceToDate = function (source, sourceTypeName) {
  1509. if (sourceTypeName === "string") {
  1510. var val = new Date(Date.parse(source));
  1511. return __isDate(val) ? val : source;
  1512. } else if (sourceTypeName === "number") {
  1513. var val = new Date(source);
  1514. return __isDate(val) ? val : source;
  1515. }
  1516. return source;
  1517. };
  1518. var coerceToBool = function (source, sourceTypeName) {
  1519. if (sourceTypeName === "string") {
  1520. var src = source.trim().toLowerCase();
  1521. if (src === 'false') {
  1522. return false;
  1523. } else if (src === "true") {
  1524. return true;
  1525. } else {
  1526. return source;
  1527. }
  1528. }
  1529. return source;
  1530. };
  1531. var DataType = new Enum("DataType", dataTypeMethods);
  1532. /**
  1533. @property String {DataType}
  1534. @final
  1535. @static
  1536. **/
  1537. DataType.String = DataType.addSymbol({ defaultValue: "", parse: coerceToString });
  1538. /**
  1539. @property Int64 {DataType}
  1540. @final
  1541. @static
  1542. **/
  1543. DataType.Int64 = DataType.addSymbol({ defaultValue: 0, isNumeric: true, isInteger: true, parse: coerceToInt });
  1544. /**
  1545. @property Int32 {DataType}
  1546. @final
  1547. @static
  1548. **/
  1549. DataType.Int32 = DataType.addSymbol({ defaultValue: 0, isNumeric: true, isInteger: true, parse: coerceToInt });
  1550. /**
  1551. @property Int16 {DataType}
  1552. @final
  1553. @static
  1554. **/
  1555. DataType.Int16 = DataType.addSymbol({ defaultValue: 0, isNumeric: true, isInteger: true, parse: coerceToInt });
  1556. /**
  1557. @property Decimal {DataType}
  1558. @final
  1559. @static
  1560. **/
  1561. DataType.Decimal = DataType.addSymbol({ defaultValue: 0, isNumeric: true, parse: coerceToFloat });
  1562. /**
  1563. @property Double {DataType}
  1564. @final
  1565. @static
  1566. **/
  1567. DataType.Double = DataType.addSymbol({ defaultValue: 0, isNumeric: true, parse: coerceToFloat });
  1568. /**
  1569. @property Single {DataType}
  1570. @final
  1571. @static
  1572. **/
  1573. DataType.Single = DataType.addSymbol({ defaultValue: 0, isNumeric: true, parse: coerceToFloat });
  1574. /**
  1575. @property DateTime {DataType}
  1576. @final
  1577. @static
  1578. **/
  1579. DataType.DateTime = DataType.addSymbol({ defaultValue: new Date(1900, 0, 1), parse: coerceToDate });
  1580. /**
  1581. @property Time {DataType}
  1582. @final
  1583. @static
  1584. **/
  1585. DataType.Time = DataType.addSymbol({ defaultValue: "PT0S" });
  1586. /**
  1587. @property Boolean {DataType}
  1588. @final
  1589. @static
  1590. **/
  1591. DataType.Boolean = DataType.addSymbol({ defaultValue: false, parse: coerceToBool });
  1592. /**
  1593. @property Guid {DataType}
  1594. @final
  1595. @static
  1596. **/
  1597. DataType.Guid = DataType.addSymbol({ defaultValue: "00000000-0000-0000-0000-000000000000" });
  1598. /**
  1599. @property Byte {DataType}
  1600. @final
  1601. @static
  1602. **/
  1603. DataType.Byte = DataType.addSymbol({ defaultValue: 0 });
  1604. /**
  1605. @property Binary {DataType}
  1606. @final
  1607. @static
  1608. **/
  1609. DataType.Binary = DataType.addSymbol({ defaultValue: null });
  1610. /**
  1611. @property Undefined {DataType}
  1612. @final
  1613. @static
  1614. **/
  1615. DataType.Undefined = DataType.addSymbol({ defaultValue: undefined });
  1616. DataType.seal();
  1617. /**
  1618. Returns the DataType for a specified EDM type name.
  1619. @method fromEdmDataType
  1620. @static
  1621. @param typeName {String}
  1622. @return {DataType} A DataType.
  1623. **/
  1624. DataType.fromEdmDataType = function (typeName) {
  1625. var dt = null;
  1626. var parts = typeName.split(".");
  1627. if (parts.length > 1) {
  1628. var simpleName = parts[1];
  1629. if (simpleName === "image") {
  1630. // hack
  1631. dt = DataType.Byte;
  1632. } else if (parts.length == 2) {
  1633. dt = DataType.fromName(simpleName);
  1634. if (!dt) {
  1635. if (simpleName === "DateTimeOffset") {
  1636. dt = DataType.DateTime;
  1637. } else {
  1638. dt = DataType.Undefined;
  1639. }
  1640. }
  1641. } else {
  1642. // enum
  1643. // dt = DataType.Int32;
  1644. dt = DataType.String;
  1645. }
  1646. }
  1647. return dt;
  1648. };
  1649. DataType.fromValue = function(val) {
  1650. if (__isDate(val)) return DataType.DateTime;
  1651. switch (typeof val) {
  1652. case "string":
  1653. if (__isGuid(val)) return DataType.Guid;
  1654. else if (__isDuration(val)) return DataType.Time;
  1655. return DataType.String;
  1656. case "boolean":
  1657. return DataType.Boolean;
  1658. case "number":
  1659. return DataType.Int32;
  1660. }
  1661. return DataType.Undefined;
  1662. };
  1663. var _localTimeRegex = /.\d{3}$/;
  1664. DataType.parseDateAsUTC = function (source) {
  1665. if (typeof source === 'string') {
  1666. // convert to UTC string if no time zone specifier.
  1667. var isLocalTime = _localTimeRegex.test(source);
  1668. source = isLocalTime ? source + 'Z' : source;
  1669. }
  1670. source = new Date(Date.parse(source));
  1671. return source;
  1672. };
  1673. // NOT YET NEEDED --------------------------------------------------
  1674. // var _utcOffsetMs = (new Date()).getTimezoneOffset() * 60000;
  1675. //DataType.parseDateAsLocal = function (source) {
  1676. // var dt = DataType.parseDatesAsUTC(source);
  1677. // if (__isDate(dt)) {
  1678. // dt = new Date(dt.getTime() + _utcOffsetMs);
  1679. // }
  1680. // return dt;
  1681. //};
  1682. // -----------------------------------------------------------------
  1683. DataType.parseDateFromServer = DataType.parseDateAsUTC;
  1684. return DataType;
  1685. }();
  1686. breeze.DataType = DataType;
  1687. /**
  1688. @module breeze
  1689. **/
  1690. var Validator = function () {
  1691. var INT16_MIN = -32768;
  1692. var INT16_MAX = 32767;
  1693. var INT32_MIN = -2147483648;
  1694. var INT32_MAX = 2147483647;
  1695. var BYTE_MIN = 0;
  1696. var BYTE_MAX = 255;
  1697. // add common props and methods for every validator 'context' here.
  1698. var rootContext = {
  1699. displayName: function (context) {
  1700. if (context.property) {
  1701. return context.property.displayName || context.propertyName || context.property.name;
  1702. } else {
  1703. return "Value";
  1704. }
  1705. }
  1706. };
  1707. /**
  1708. Instances of the Validator class provide the logic to validate another object and provide a description of any errors
  1709. encountered during the validation process. They are typically associated with a 'validators' property on the following types: {{#crossLink "EntityType"}}{{/crossLink}},
  1710. {{#crossLink "DataProperty"}}{{/crossLink}} or {{#crossLink "NavigationProperty"}}{{/crossLink}}.
  1711. A number of property level validators are registered automatically, i.e added to each DataProperty.validators property
  1712. based on {{#crossLink "DataProperty"}}{{/crossLink}} metadata. For example,
  1713. - DataProperty.dataType -> one of the 'dataType' validator methods such as Validator.int64, Validator.date, Validator.bool etc.
  1714. - DataProperty.maxLength -> Validator.maxLength
  1715. - DataProperty.isNullable -> Validator.required (if not nullable)
  1716. @class Validator
  1717. **/
  1718. /**
  1719. Validator constructor - This method is used to create create custom validations. Several
  1720. basic "Validator" construction methods are also provided as static methods to this class. These methods
  1721. provide a simpler syntax for creating basic validations.
  1722. However, sometimes a custom validator will be required.
  1723. @example
  1724. Most validators will be 'property' level validators, like this.
  1725. @example
  1726. // v is this function is the value to be validated, in this case a "country" string.
  1727. var valFn = function (v) {
  1728. if (v == null) return true;
  1729. return (core.stringStartsWith(v, "US"));
  1730. };
  1731. var countryValidator = new Validator("countryIsUS", valFn, {
  1732. displayName: "Country",
  1733. messageTemplate: "'%displayName%' must start with 'US'"
  1734. });
  1735. // Now plug it into Breeze.
  1736. // Assume em1 is a preexisting EntityManager.
  1737. var custType = metadataStore.getEntityType("Customer");
  1738. var countryProp = custType.getProperty("Country");
  1739. // Note that validator is added to a 'DataProperty' validators collection.
  1740. prop.validators.push(countryValidator);
  1741. Entity level validators are also possible
  1742. @example
  1743. function isValidZipCode(value) {
  1744. var re = /^\d{5}([\-]\d{4})?$/;
  1745. return (re.test(value));
  1746. }
  1747. // v in this case will be a Customer entity
  1748. var valFn = function (v) {
  1749. // This validator only validates US Zip Codes.
  1750. if ( v.getProperty("Country") === "USA") {
  1751. var postalCode = v.getProperty("PostalCode");
  1752. return isValidZipCode(postalCode);
  1753. }
  1754. return true;
  1755. };
  1756. var zipCodeValidator = new Validator("zipCodeValidator", valFn,
  1757. { messageTemplate: "For the US, this is not a valid PostalCode" });
  1758. // Now plug it into Breeze.
  1759. // Assume em1 is a preexisting EntityManager.
  1760. var custType = em1.metadataStore.getEntityType("Customer");
  1761. // Note that validator is added to an 'EntityType' validators collection.
  1762. custType.validators.push(zipCodeValidator);
  1763. What is commonly needed is a way of creating a parameterized function that will itself
  1764. return a new Validator. This requires the use of a 'context' object.
  1765. @example
  1766. // create a function that will take in a config object
  1767. // and will return a validator
  1768. var numericRangeValidator = function(context) {
  1769. var valFn = function(v, ctx) {
  1770. if (v == null) return true;
  1771. if (typeof(v) !== "number") return false;
  1772. if (ctx.min != null && v < ctx.min) return false;
  1773. if (ctx.max != null && v > ctx.max) return false;
  1774. return true;
  1775. };
  1776. // The last parameter below is the 'context' object that will be passed into the 'ctx' parameter above
  1777. // when this validator executes. Several other properties, such as displayName will get added to this object as well.
  1778. return new Validator("numericRange", valFn, {
  1779. messageTemplate: "'%displayName%' must be an integer between the values of %min% and %max%",
  1780. min: context.min,
  1781. max: context.max
  1782. });
  1783. };
  1784. // Assume that freightProperty is a DataEntityProperty that describes numeric values.
  1785. // register the validator
  1786. freightProperty.validators.push(numericRangeValidator({ min: 100, max: 500 }));
  1787. @method <ctor> Validator
  1788. @param name {String} The name of this validator.
  1789. @param validatorFn {Function} A function to perform validation.
  1790. validatorFn(value, context)
  1791. @param validatorFn.value {Object} Value to be validated
  1792. @param validatorFn.context {Object} The same context object passed into the constructor with the following additonal properties if not
  1793. otherwise specified.
  1794. @param validatorFn.context.value {Object} The value being validated.
  1795. @param validatorFn.context.validatorName {String} The name of the validator being executed.
  1796. @param validatorFn.context.displayName {String} This will be either the value of the property's 'displayName' property or
  1797. the value of its 'name' property or the string 'Value'
  1798. @param validatorFn.context.messageTemplate {String} This will either be the value of Validator.messageTemplates[ {this validators name}] or null. Validator.messageTemplates
  1799. is an object that is keyed by validator name and that can be added to in order to 'register' your own message for a given validator.
  1800. The following property can also be specified for any validator to force a specific errorMessage string
  1801. @param [validatorFn.context.message] {String} If this property is set it will be used instead of the 'messageTemplate' property when an
  1802. error message is generated.
  1803. @param [context] {Object} A free form object whose properties will made available during the validation and error message creation process.
  1804. This object will be passed into the Validator's validation function whenever 'validate' is called. See above for a description
  1805. of additional properties that will be automatically added to this object if not otherwise specified.
  1806. **/
  1807. var ctor = function (name, valFn, context) {
  1808. // _baseContext is what will get serialized
  1809. this._baseContext = context || {};
  1810. this._baseContext.validatorName = name;
  1811. context = __extend(Object.create(rootContext), this._baseContext);
  1812. context.messageTemplate = context.messageTemplate || ctor.messageTemplates[name];
  1813. this.name = name;
  1814. this.valFn = valFn;
  1815. this.context = context;
  1816. };
  1817. var proto = ctor.prototype;
  1818. proto._$typeName = "Validator";
  1819. /**
  1820. Run this validator against the specified value. This method will usually be called internally either
  1821. automatically by an property change, entity attach, query or save operation, or manually as a result of
  1822. a validateEntity call on the EntityAspect. The resulting ValidationResults are available via the
  1823. EntityAspect.getValidationErrors method.
  1824. However, you can also call a validator directly either for testing purposes or some other reason if needed.
  1825. @example
  1826. // using one of the predefined validators
  1827. var validator = Validator.maxLength({ maxLength: 5, displayName: "City" });
  1828. // should be ok because "asdf".length < 5
  1829. var result = validator.validate("asdf");
  1830. ok(result === null);
  1831. result = validator.validate("adasdfasdf");
  1832. // extract all of the properties of the 'result'
  1833. var errMsg = result.errorMessage;
  1834. var context = result.context;
  1835. var sameValidator = result.validator;
  1836. @method validate
  1837. @param value {Object} Value to validate
  1838. @param additionalContext {Object} Any additional contextual information that the Validator
  1839. can make use of.
  1840. @return {ValidationError|null} A ValidationError if validation fails, null otherwise
  1841. **/
  1842. proto.validate = function (value, additionalContext) {
  1843. var currentContext;
  1844. if (additionalContext) {
  1845. currentContext = __extend(Object.create(this.context), additionalContext);
  1846. } else {
  1847. currentContext = this.context;
  1848. }
  1849. this.currentContext = currentContext;
  1850. if (!this.valFn(value, currentContext)) {
  1851. currentContext.value = value;
  1852. return new ValidationError(this, currentContext, this.getMessage());
  1853. }
  1854. return null;
  1855. };
  1856. // context.value is not avail unless validate was called first.
  1857. /**
  1858. Returns the message generated by the most recent execution of this Validator.
  1859. @example
  1860. var v0 = Validator.maxLength({ maxLength: 5, displayName: "City" });
  1861. v0.validate("adasdfasdf");
  1862. var errMessage = v0.getMessage());
  1863. @method getMessage
  1864. @return {String}
  1865. **/
  1866. proto.getMessage = function () {
  1867. try {
  1868. var context = this.currentContext;
  1869. var message = context.message;
  1870. if (message) {
  1871. if (typeof (message) == "function") {
  1872. return message(context);
  1873. } else {
  1874. return message;
  1875. }
  1876. } else if (context.messageTemplate) {
  1877. return formatTemplate(context.messageTemplate, context);
  1878. } else {
  1879. return "invalid value: " + this.validatorName || "{unnamed validator}";
  1880. }
  1881. } catch (e) {
  1882. return "Unable to format error message" + e.toString();
  1883. }
  1884. };
  1885. proto.toJSON = function () {
  1886. return this._baseContext;
  1887. };
  1888. ctor.fromJSON = function (json) {
  1889. var validatorName = "Validator." + json.validatorName;
  1890. var fn = __config.functionRegistry[validatorName];
  1891. if (!fn) {
  1892. throw new Error("Unable to locate a validator named:" + json.validatorName);
  1893. }
  1894. return fn(json);
  1895. };
  1896. /**
  1897. Register a validator instance so that any deserialized metadata can reference it.
  1898. @method register
  1899. @param validator {Validator} Validator to register.
  1900. **/
  1901. ctor.register = function(validator) {
  1902. __config.registerFunction(function () { return validator; }, "Validator." + validator.name);
  1903. };
  1904. /**
  1905. Register a validator factory so that any deserialized metadata can reference it.
  1906. @method registerFactory
  1907. @param validatorFactory {Function} A function that optionally takes a context property and returns a Validator instance.
  1908. **/
  1909. ctor.registerFactory = function(validatorFn, name) {
  1910. __config.registerFunction(validatorFn, "Validator." + name);
  1911. };
  1912. /**
  1913. Map of standard error message templates keyed by validator name.
  1914. You can add to or modify this object to customize the template used for any validation error message.
  1915. @example
  1916. // v is this function is the value to be validated, in this case a "country" string.
  1917. var valFn = function (v) {
  1918. if (v == null) return true;
  1919. return (core.stringStartsWith(v, "US"));
  1920. };
  1921. var countryValidator = new Validator("countryIsUS", valFn, { displayName: "Country" });
  1922. Validator.messageTemplates["countryIsUS", "'%displayName%' must start with 'US'");
  1923. This will have a similar effect to this
  1924. var countryValidator = new Validator("countryIsUS", valFn, {
  1925. displayName: "Country",
  1926. messageTemplate: "'%displayName%' must start with 'US'"
  1927. });
  1928. @property messageTemplates {Object}
  1929. @static
  1930. **/
  1931. ctor.messageTemplates = {
  1932. required: "'%displayName%' is required",
  1933. date: "'%displayName%' must be a date",
  1934. string: "'%displayName%' must be a string",
  1935. bool: "'%displayName%' must be a 'true' or 'false' value",
  1936. guid: "'%displayName%' must be a GUID",
  1937. duration: "'%displayName%' must be a ISO8601 duration string, such as 'P3H24M60S'",
  1938. number: "'%displayName%' must be a number",
  1939. integer: "'%displayName%' must be an integer",
  1940. integerRange: "'%displayName%' must be an integer between the values of %minValue% and %maxValue%",
  1941. maxLength: "'%displayName%' must be a string with less than %maxLength% characters",
  1942. stringLength: "'%displayName%' must be a string with between %minLength% and %maxLength% characters"
  1943. };
  1944. /**
  1945. Returns a standard 'required value' Validator
  1946. @example
  1947. // Assume em1 is a preexisting EntityManager.
  1948. var custType = em1.metadataStore.getEntityType("Customer");
  1949. var regionProperty - custType.getProperty("Region");
  1950. // Makes "Region" on Customer a required property.
  1951. regionProperty.validators.push(Validator.required());
  1952. @method required
  1953. @static
  1954. @return {Validator} A new Validator
  1955. **/
  1956. ctor.required = function () {
  1957. var valFn = function (v, ctx) {
  1958. if (typeof v === "string") {
  1959. if (ctx && ctx.allowEmptyStrings) return true;
  1960. return v.length > 0;
  1961. } else {
  1962. return v != null;
  1963. }
  1964. };
  1965. return new ctor("required", valFn);
  1966. };
  1967. /**
  1968. Returns a standard maximum string length Validator; the maximum length must be specified
  1969. @example
  1970. // Assume em1 is a preexisting EntityManager.
  1971. var custType = em1.metadataStore.getEntityType("Customer");
  1972. var regionProperty - custType.getProperty("Region");
  1973. // Validates that the value of the Region property on Customer will be less than or equal to 5 characters.
  1974. regionProperty.validators.push(Validator.maxLength( {maxLength: 5}));
  1975. @method maxLength
  1976. @static
  1977. @param context {Object}
  1978. @param context.maxLength {Integer}
  1979. @return {Validator} A new Validator
  1980. **/
  1981. ctor.maxLength = function (context) {
  1982. var valFn = function (v, ctx) {
  1983. if (v == null) return true;
  1984. if (typeof (v) != "string") return false;
  1985. return v.length <= ctx.maxLength;
  1986. };
  1987. return new ctor("maxLength", valFn, context);
  1988. };
  1989. /**
  1990. Returns a standard string length Validator; both minimum and maximum lengths must be specified.
  1991. @example
  1992. // Assume em1 is a preexisting EntityManager.
  1993. var custType = em1.metadataStore.getEntityType("Customer");
  1994. var regionProperty - custType.getProperty("Region");
  1995. // Validates that the value of the Region property on Customer will be
  1996. // between 2 and 5 characters
  1997. regionProperty.validators.push(Validator.stringLength( {minLength: 2, maxLength: 5});
  1998. @method stringLength
  1999. @static
  2000. @param context {Object}
  2001. @param context.maxLength {Integer}
  2002. @param context.minLength {Integer}
  2003. @return {Validator} A new Validator
  2004. **/
  2005. ctor.stringLength = function (context) {
  2006. var valFn = function (v, ctx) {
  2007. if (v == null) return true;
  2008. if (typeof (v) != "string") return false;
  2009. if (ctx.minLength != null && v.length < ctx.minLength) return false;
  2010. if (ctx.maxLength != null && v.length > ctx.maxLength) return false;
  2011. return true;
  2012. };
  2013. return new ctor("stringLength", valFn, context);
  2014. };
  2015. /**
  2016. Returns a standard string dataType Validator.
  2017. @example
  2018. // Assume em1 is a preexisting EntityManager.
  2019. var custType = em1.metadataStore.getEntityType("Customer");
  2020. var regionProperty - custType.getProperty("Region");
  2021. // Validates that the value of the Region property on Customer is a string.
  2022. regionProperty.validators.push(Validator.string());
  2023. @method string
  2024. @static
  2025. @return {Validator} A new Validator
  2026. **/
  2027. ctor.string = function () {
  2028. var valFn = function (v) {
  2029. if (v == null) return true;
  2030. return (typeof v === "string");
  2031. };
  2032. return new ctor("string", valFn );
  2033. };
  2034. /**
  2035. Returns a Guid data type Validator.
  2036. @example
  2037. // Assume em1 is a preexisting EntityManager.
  2038. var custType = em1.metadataStore.getEntityType("Customer");
  2039. var customerIdProperty - custType.getProperty("CustomerID");
  2040. // Validates that the value of the CustomerID property on Customer is a Guid.
  2041. customerIdProperty.validators.push(Validator.guid());
  2042. @method guid
  2043. @static
  2044. @return {Validator} A new Validator
  2045. **/
  2046. ctor.guid = function () {
  2047. var valFn = function (v) {
  2048. if (v == null) return true;
  2049. return __isGuid(v);
  2050. };
  2051. return new ctor("guid", valFn);
  2052. };
  2053. /**
  2054. Returns a ISO 8601 duration string Validator.
  2055. @example
  2056. // Assume em1 is a preexisting EntityManager.
  2057. var eventType = em1.metadataStore.getEntityType("Event");
  2058. var elapsedTimeProperty - eventType.getProperty("ElapsedTime");
  2059. // Validates that the value of the ElapsedTime property on Customer is a duration.
  2060. elapsedTimeProperty.validators.push(Validator.duration());
  2061. @method duration
  2062. @static
  2063. @return {Validator} A new Validator
  2064. **/
  2065. ctor.duration = function() {
  2066. var valFn = function(v) {
  2067. if (v == null) return true;
  2068. return __isDuration(v);
  2069. };
  2070. return new ctor("duration", valFn);
  2071. };
  2072. /**
  2073. Returns a standard numeric data type Validator.
  2074. @example
  2075. // Assume em1 is a preexisting EntityManager.
  2076. var orderType = em1.metadataStore.getEntityType("Order");
  2077. var freightProperty - orderType.getProperty("Freight");
  2078. // Validates that the value of the Freight property on Order is a number.
  2079. freightProperty.validators.push(Validator.number());
  2080. @method number
  2081. @static
  2082. @return {Validator} A new Validator
  2083. **/
  2084. // TODO: may need to have seperate logic for single.
  2085. ctor.number = ctor.double = ctor.single = function (context) {
  2086. var valFn = function (v, ctx) {
  2087. if (v == null) return true;
  2088. if (typeof v === "string" && ctx && ctx.allowString) {
  2089. v = parseInt(v, 10);
  2090. }
  2091. return (typeof v === "number" && !isNaN(v));
  2092. };
  2093. return new ctor("number", valFn, context);
  2094. };
  2095. /**
  2096. Returns a standard large integer data type - 64 bit - Validator.
  2097. @example
  2098. // Assume em1 is a preexisting EntityManager.
  2099. var orderType = em1.metadataStore.getEntityType("Order");
  2100. var freightProperty - orderType.getProperty("Freight");
  2101. // Validates that the value of the Freight property on Order is within the range of a 64 bit integer.
  2102. freightProperty.validators.push(Validator.int64());
  2103. @method int64
  2104. @static
  2105. @return {Validator} A new Validator
  2106. **/
  2107. ctor.integer = ctor.int64 = function (context) {
  2108. var valFn = function (v, ctx) {
  2109. if (v == null) return true;
  2110. if (typeof v === "string" && ctx && ctx.allowString) {
  2111. v = parseInt(v, 10);
  2112. }
  2113. return (typeof v === "number") && (!isNaN(v)) && Math.floor(v) === v;
  2114. };
  2115. return new ctor("integer", valFn, context );
  2116. };
  2117. /**
  2118. Returns a standard 32 bit integer data type Validator.
  2119. @example
  2120. // Assume em1 is a preexisting EntityManager.
  2121. var orderType = em1.metadataStore.getEntityType("Order");
  2122. var freightProperty - orderType.getProperty("Freight");
  2123. freightProperty.validators.push(Validator.int32());
  2124. @method int32
  2125. @static
  2126. @return {Validator} A new Validator
  2127. **/
  2128. ctor.int32 = function(context) {
  2129. return intRangeValidatorCtor("int32", INT32_MIN, INT32_MAX, context)();
  2130. };
  2131. /**
  2132. Returns a standard 16 bit integer data type Validator.
  2133. @example
  2134. // Assume em1 is a preexisting EntityManager.
  2135. var orderType = em1.metadataStore.getEntityType("Order");
  2136. var freightProperty - orderType.getProperty("Freight");
  2137. // Validates that the value of the Freight property on Order is within the range of a 16 bit integer.
  2138. freightProperty.validators.push(Validator.int16());
  2139. @method int16
  2140. @static
  2141. @return {Validator} A new Validator
  2142. **/
  2143. ctor.int16 = function (context) {
  2144. return intRangeValidatorCtor("int16", INT16_MIN, INT16_MAX, context)();
  2145. };
  2146. /**
  2147. Returns a standard byte data type Validator. (This is a integer between 0 and 255 inclusive for js purposes).
  2148. @example
  2149. // Assume em1 is a preexisting EntityManager.
  2150. var orderType = em1.metadataStore.getEntityType("Order");
  2151. var freightProperty - orderType.getProperty("Freight");
  2152. // Validates that the value of the Freight property on Order is within the range of a 16 bit integer.
  2153. // Probably not a very good validation to place on the Freight property.
  2154. regionProperty.validators.push(Validator.byte());
  2155. @method byte
  2156. @static
  2157. @return {Validator} A new Validator
  2158. **/
  2159. ctor.byte = function (context) {
  2160. return intRangeValidatorCtor("byte", BYTE_MIN, BYTE_MAX, context)();
  2161. };
  2162. /**
  2163. Returns a standard boolean data type Validator.
  2164. @example
  2165. // Assume em1 is a preexisting EntityManager.
  2166. var productType = em1.metadataStore.getEntityType("Product");
  2167. var discontinuedProperty - productType.getProperty("Discontinued");
  2168. // Validates that the value of the Discontinued property on Product is a boolean
  2169. discontinuedProperty.validators.push(Validator.bool());
  2170. @method bool
  2171. @static
  2172. @return {Validator} A new Validator
  2173. **/
  2174. ctor.bool = function () {
  2175. var valFn = function (v) {
  2176. if (v == null) return true;
  2177. return (v === true) || (v === false);
  2178. };
  2179. return new ctor("bool", valFn );
  2180. };
  2181. ctor.none = function () {
  2182. var valFn = function (v) {
  2183. return true;
  2184. };
  2185. return new ctor("none", valFn);
  2186. };
  2187. /**
  2188. Returns a standard date data type Validator.
  2189. @example
  2190. // Assume em1 is a preexisting EntityManager.
  2191. var orderType = em1.metadataStore.getEntityType("Order");
  2192. var orderDateProperty - orderType.getProperty("OrderDate");
  2193. // Validates that the value of the OrderDate property on Order is a date
  2194. // Probably not a very good validation to place on the Freight property.
  2195. orderDateProperty.validators.push(Validator.date());
  2196. @method date
  2197. @static
  2198. @return {Validator} A new Validator
  2199. **/
  2200. ctor.date = function () {
  2201. var valFn = function (v) {
  2202. if (v == null) return true;
  2203. if (typeof v === "string") {
  2204. try {
  2205. return !isNaN(Date.parse(v));
  2206. // old code
  2207. // return __isDate(new Date(v));
  2208. } catch (e) {
  2209. return false;
  2210. }
  2211. } else {
  2212. return __isDate(v);
  2213. }
  2214. };
  2215. return new ctor("date", valFn );
  2216. };
  2217. // register all validators
  2218. __objectForEach(ctor, function (key, value) {
  2219. if (typeof (value) !== "function") {
  2220. return;
  2221. }
  2222. if (key === "fromJSON" || key === "register" || key === "registerFactory") {
  2223. return;
  2224. }
  2225. __config.registerFunction(value, "Validator." + key);
  2226. });
  2227. // private funcs
  2228. function formatTemplate(template, vars, ownPropertiesOnly) {
  2229. if (!vars) return template;
  2230. return template.replace(/%([^%]+)%/g, function (_, key) {
  2231. var valOrFn;
  2232. if (ownPropertiesOnly) {
  2233. valOrFn = vars.hasOwnProperty(key) ? vars[key] : '';
  2234. } else {
  2235. valOrFn = vars[key];
  2236. }
  2237. if (valOrFn) {
  2238. if (__isFunction(valOrFn)) {
  2239. return valOrFn(vars);
  2240. } else {
  2241. return valOrFn;
  2242. }
  2243. } else {
  2244. return "";
  2245. }
  2246. });
  2247. }
  2248. function intRangeValidatorCtor(validatorName, minValue, maxValue, context) {
  2249. ctor.messageTemplates[validatorName] = __formatString("'%displayName%' must be an integer between the values of %1 and %2",
  2250. minValue, maxValue);
  2251. return function () {
  2252. var valFn = function (v, ctx) {
  2253. if (v == null) return true;
  2254. if (typeof v === "string" && ctx && ctx.allowString) {
  2255. v = parseInt(v, 0);
  2256. }
  2257. if ((typeof v === "number") && (!isNaN(v)) && Math.floor(v) === v) {
  2258. if (minValue != null && v < minValue) {
  2259. return false;
  2260. }
  2261. if (maxValue != null && v > maxValue) {
  2262. return false;
  2263. }
  2264. return true;
  2265. } else {
  2266. return false;
  2267. }
  2268. };
  2269. return new ctor(validatorName, valFn, context);
  2270. };
  2271. }
  2272. return ctor;
  2273. } ();
  2274. var ValidationError = function () {
  2275. /**
  2276. A ValidatationError is used to describe a failed validation.
  2277. @class ValidationError
  2278. **/
  2279. /**
  2280. @method <ctor> ValidationError
  2281. @param validator {Validator}
  2282. @param context {Object}
  2283. @param errorMessage {String}
  2284. **/
  2285. var ctor = function (validator, context, errorMessage) {
  2286. assertParam(validator, "validator").isString().or().isInstanceOf(Validator).check();
  2287. this.validator = validator;
  2288. context = context || {};
  2289. this.context = context;
  2290. this.property = context.property;
  2291. if (this.property) {
  2292. this.propertyName = context.propertyName || context.property.name;
  2293. }
  2294. this.errorMessage = errorMessage;
  2295. this.key = ValidationError.getKey(validator, this.propertyName);
  2296. };
  2297. /**
  2298. The Validator associated with this ValidationError.
  2299. __readOnly__
  2300. @property validator {Validator}
  2301. **/
  2302. /**
  2303. A 'context' object associated with this ValidationError.
  2304. __readOnly__
  2305. @property context {Object}
  2306. **/
  2307. /**
  2308. The DataProperty or NavigationProperty associated with this ValidationError.
  2309. __readOnly__
  2310. @property property {DataProperty|NavigationProperty}
  2311. **/
  2312. /**
  2313. The property name associated with this ValidationError. This will be a "property path" for any properties of a complex object.
  2314. __readOnly__
  2315. @property propertyName {String}
  2316. **/
  2317. /**
  2318. The error message associated with the ValidationError.
  2319. __readOnly__
  2320. @property errorMessage {string}
  2321. **/
  2322. ctor.getKey = function (validator, propertyPath) {
  2323. return (propertyPath || "") + ":" + validator.name;
  2324. };
  2325. return ctor;
  2326. }();
  2327. DataType.getSymbols().forEach(function (sym) {
  2328. sym.validatorCtor = getValidatorCtor(sym);
  2329. });
  2330. function getValidatorCtor(symbol) {
  2331. switch (symbol) {
  2332. case DataType.String:
  2333. return Validator.string;
  2334. case DataType.Int64:
  2335. return Validator.int64;
  2336. case DataType.Int32:
  2337. return Validator.int32;
  2338. case DataType.Int16:
  2339. return Validator.int16;
  2340. case DataType.Decimal:
  2341. return Validator.number;
  2342. case DataType.Double:
  2343. return Validator.number;
  2344. case DataType.Single:
  2345. return Validator.number;
  2346. case DataType.DateTime:
  2347. return Validator.date;
  2348. case DataType.Boolean:
  2349. return Validator.bool;
  2350. case DataType.Guid:
  2351. return Validator.guid;
  2352. case DataType.Byte:
  2353. return Validator.byte;
  2354. case DataType.Binary:
  2355. // TODO: don't quite know how to validate this yet.
  2356. return Validator.none;
  2357. case DataType.Time:
  2358. return Validator.duration;
  2359. case DataType.Undefined:
  2360. return Validator.none;
  2361. }
  2362. };
  2363. breeze.Validator = Validator;
  2364. breeze.ValidationError = ValidationError;
  2365. /**
  2366. @module breeze
  2367. **/
  2368. var EntityState = (function () {
  2369. /**
  2370. EntityState is an 'Enum' containing all of the valid states for an 'Entity'.
  2371. @class EntityState
  2372. @static
  2373. **/
  2374. var entityStateMethods = {
  2375. /**
  2376. @example
  2377. var es = anEntity.entityAspect.entityState;
  2378. return es.isUnchanged();
  2379. is the same as
  2380. @example
  2381. return es === EntityState.Unchanged;
  2382. @method isUnchanged
  2383. @return {Boolean} Whether an entityState instance is EntityState.Unchanged.
  2384. **/
  2385. isUnchanged: function () { return this === EntityState.Unchanged; },
  2386. /**
  2387. @example
  2388. var es = anEntity.entityAspect.entityState;
  2389. return es.isAdded();
  2390. is the same as
  2391. @example
  2392. return es === EntityState.Added;
  2393. @method isAdded
  2394. @return {Boolean} Whether an entityState instance is EntityState.Added.
  2395. **/
  2396. isAdded: function () { return this === EntityState.Added; },
  2397. /**
  2398. @example
  2399. var es = anEntity.entityAspect.entityState;
  2400. return es.isModified();
  2401. is the same as
  2402. @example
  2403. return es === EntityState.Modified;
  2404. @method isModified
  2405. @return {Boolean} Whether an entityState instance is EntityState.Modified.
  2406. **/
  2407. isModified: function () { return this === EntityState.Modified; },
  2408. /**
  2409. @example
  2410. var es = anEntity.entityAspect.entityState;
  2411. return es.isDeleted();
  2412. is the same as
  2413. @example
  2414. return es === EntityState.Deleted;
  2415. @method isDeleted
  2416. @return {Boolean} Whether an entityState instance is EntityState.Deleted.
  2417. **/
  2418. isDeleted: function () { return this === EntityState.Deleted; },
  2419. /**
  2420. @example
  2421. var es = anEntity.entityAspect.entityState;
  2422. return es.isDetached();
  2423. is the same as
  2424. @example
  2425. return es === EntityState.Detached;
  2426. @method isDetached
  2427. @return {Boolean} Whether an entityState instance is EntityState.Detached.
  2428. **/
  2429. isDetached: function () { return this === EntityState.Detached; },
  2430. /**
  2431. @example
  2432. var es = anEntity.entityAspect.entityState;
  2433. return es.isUnchangedOrModified();
  2434. is the same as
  2435. @example
  2436. return es === EntityState.Unchanged || es === EntityState.Modified
  2437. @method isUnchangedOrModified
  2438. @return {Boolean} Whether an entityState instance is EntityState.Unchanged or EntityState.Modified.
  2439. **/
  2440. isUnchangedOrModified: function () {
  2441. return this === EntityState.Unchanged || this === EntityState.Modified;
  2442. },
  2443. /**
  2444. @example
  2445. var es = anEntity.entityAspect.entityState;
  2446. return es.isAddedModifiedOrDeleted();
  2447. is the same as
  2448. @example
  2449. return es === EntityState.Added || es === EntityState.Modified || es === EntityState.Deleted
  2450. @method isAddedModifiedOrDeleted
  2451. @return {Boolean} Whether an entityState instance is EntityState.Unchanged or EntityState.Modified or EntityState.Deleted.
  2452. **/
  2453. isAddedModifiedOrDeleted: function () {
  2454. return this === EntityState.Added ||
  2455. this === EntityState.Modified ||
  2456. this === EntityState.Deleted;
  2457. }
  2458. };
  2459. var EntityState = new Enum("EntityState", entityStateMethods);
  2460. /**
  2461. The 'Unchanged' state.
  2462. @property Unchanged {EntityState}
  2463. @final
  2464. @static
  2465. **/
  2466. EntityState.Unchanged = EntityState.addSymbol();
  2467. /**
  2468. The 'Added' state.
  2469. @property Added {EntityState}
  2470. @final
  2471. @static
  2472. **/
  2473. EntityState.Added = EntityState.addSymbol();
  2474. /**
  2475. The 'Modified' state.
  2476. @property Modified {EntityState}
  2477. @final
  2478. @static
  2479. **/
  2480. EntityState.Modified = EntityState.addSymbol();
  2481. /**
  2482. The 'Deleted' state.
  2483. @property Deleted {EntityState}
  2484. @final
  2485. @static
  2486. **/
  2487. EntityState.Deleted = EntityState.addSymbol();
  2488. /**
  2489. The 'Detached' state.
  2490. @property Detached {EntityState}
  2491. @final
  2492. @static
  2493. **/
  2494. EntityState.Detached = EntityState.addSymbol();
  2495. EntityState.seal();
  2496. return EntityState;
  2497. })();
  2498. var EntityAction = (function () {
  2499. /**
  2500. EntityAction is an 'Enum' containing all of the valid actions that can occur to an 'Entity'.
  2501. @class EntityAction
  2502. @static
  2503. **/
  2504. var entityActionMethods = {
  2505. isAttach: function () { return !!this.isAttach; },
  2506. isDetach: function () { return !!this.isDetach; },
  2507. isModification: function () { return !!this.isModification; }
  2508. };
  2509. var EntityAction = new Enum("EntityAction", entityActionMethods);
  2510. /**
  2511. Attach - Entity was attached via an AttachEntity call.
  2512. @property Attach {EntityAction}
  2513. @final
  2514. @static
  2515. **/
  2516. EntityAction.Attach = EntityAction.addSymbol({ isAttach: true});
  2517. /**
  2518. AttachOnQuery - Entity was attached as a result of a query.
  2519. @property AttachOnQuery {EntityAction}
  2520. @final
  2521. @static
  2522. **/
  2523. EntityAction.AttachOnQuery = EntityAction.addSymbol({ isAttach: true});
  2524. /**
  2525. AttachOnImport - Entity was attached as a result of an import.
  2526. @property AttachOnImport {EntityAction}
  2527. @final
  2528. @static
  2529. **/
  2530. EntityAction.AttachOnImport = EntityAction.addSymbol({ isAttach: true});
  2531. /**
  2532. AttachOnQuery - Entity was detached.
  2533. @property Detach {EntityAction}
  2534. @final
  2535. @static
  2536. **/
  2537. EntityAction.Detach = EntityAction.addSymbol( { isDetach: true });
  2538. /**
  2539. MergeOnQuery - Properties on the entity were merged as a result of a query.
  2540. @property MergeOnQuery {EntityAction}
  2541. @final
  2542. @static
  2543. **/
  2544. EntityAction.MergeOnQuery = EntityAction.addSymbol( { isModification: true });
  2545. /**
  2546. MergeOnImport - Properties on the entity were merged as a result of an import.
  2547. @property MergeOnImport {EntityAction}
  2548. @final
  2549. @static
  2550. **/
  2551. EntityAction.MergeOnImport = EntityAction.addSymbol( { isModification: true });
  2552. /**
  2553. MergeOnImport - Properties on the entity were merged as a result of a save
  2554. @property MergeOnImport {EntityAction}
  2555. @final
  2556. @static
  2557. **/
  2558. EntityAction.MergeOnSave = EntityAction.addSymbol( { isModification: true });
  2559. /**
  2560. PropertyChange - A property on the entity was changed.
  2561. @property PropertyChange {EntityAction}
  2562. @final
  2563. @static
  2564. **/
  2565. EntityAction.PropertyChange = EntityAction.addSymbol({ isModification: true});
  2566. /**
  2567. EntityStateChange - The EntityState of the entity was changed.
  2568. @property EntityStateChange {EntityAction}
  2569. @final
  2570. @static
  2571. **/
  2572. EntityAction.EntityStateChange = EntityAction.addSymbol();
  2573. /**
  2574. AcceptChanges - AcceptChanges was called on the entity, or its entityState was set to Unmodified.
  2575. @property AcceptChanges {EntityAction}
  2576. @final
  2577. @static
  2578. **/
  2579. EntityAction.AcceptChanges = EntityAction.addSymbol();
  2580. /**
  2581. RejectChanges - RejectChanges was called on the entity.
  2582. @property RejectChanges {EntityAction}
  2583. @final
  2584. @static
  2585. **/
  2586. EntityAction.RejectChanges = EntityAction.addSymbol({ isModification: true});
  2587. /**
  2588. Clear - The EntityManager was cleared. All entities detached.
  2589. @property Clear {EntityAction}
  2590. @final
  2591. @static
  2592. **/
  2593. EntityAction.Clear = EntityAction.addSymbol({ isDetach: true});
  2594. EntityAction.seal();
  2595. return EntityAction;
  2596. })();
  2597. var EntityAspect = function() {
  2598. /**
  2599. An EntityAspect instance is associated with every attached entity and is accessed via the entity's 'entityAspect' property.
  2600. The EntityAspect itself provides properties to determine and modify the EntityState of the entity and has methods
  2601. that provide a variety of services including validation and change tracking.
  2602. An EntityAspect will almost never need to be constructed directly. You will usually get an EntityAspect by accessing
  2603. an entities 'entityAspect' property. This property will be automatically attached when an entity is created via either
  2604. a query, import or EntityManager.createEntity call.
  2605. // assume order is an order entity attached to an EntityManager.
  2606. var aspect = order.entityAspect;
  2607. var currentState = aspect.entityState;
  2608. @class EntityAspect
  2609. **/
  2610. var ctor = function(entity) {
  2611. if (entity === null) {
  2612. var nullInstance = EntityAspect._nullInstance;
  2613. if (nullInstance) return nullInstance;
  2614. EntityAspect._nullInstance = this;
  2615. } else if (entity === undefined) {
  2616. throw new Error("The EntityAspect ctor requires an entity as its only argument.");
  2617. } else if (entity.entityAspect) {
  2618. return entity.entityAspect;
  2619. }
  2620. // if called without new
  2621. if (!(this instanceof EntityAspect)) {
  2622. return new EntityAspect(entity);
  2623. }
  2624. this.entity = entity;
  2625. // TODO: keep public or not?
  2626. this.entityGroup = null;
  2627. this.entityManager = null;
  2628. this.entityState = EntityState.Detached;
  2629. this.isBeingSaved = false;
  2630. this.originalValues = {};
  2631. this._validationErrors = {};
  2632. this.validationErrorsChanged = new Event("validationErrorsChanged_entityAspect", this);
  2633. this.propertyChanged = new Event("propertyChanged_entityAspect", this);
  2634. // in case this is the NULL entityAspect. - used with ComplexAspects that have no parent.
  2635. if (entity != null) {
  2636. entity.entityAspect = this;
  2637. // entityType should already be on the entity from 'watch'
  2638. var entityType = entity.entityType;
  2639. if (!entityType) {
  2640. var typeName = entity.prototype._$typeName;
  2641. if (!typeName) {
  2642. throw new Error("This entity is not registered as a valid EntityType");
  2643. } else {
  2644. throw new Error("Metadata for this entityType has not yet been resolved: " + typeName);
  2645. }
  2646. }
  2647. var entityCtor = entityType.getEntityCtor();
  2648. __modelLibraryDef.getDefaultInstance().startTracking(entity, entityCtor.prototype);
  2649. }
  2650. };
  2651. var proto = ctor.prototype;
  2652. proto._postInitialize = function() {
  2653. var entity = this.entity;
  2654. var entityCtor = entity.entityType.getEntityCtor();
  2655. var initFn = entityCtor._$initializationFn;
  2656. if (initFn) {
  2657. if (typeof initFn === "string") {
  2658. initFn = entity[initFn];
  2659. }
  2660. initFn(entity);
  2661. }
  2662. };
  2663. Event.bubbleEvent(proto, function() {
  2664. return this.entityManager;
  2665. });
  2666. /**
  2667. The Entity that this aspect is associated with.
  2668. __readOnly__
  2669. @property entity {Entity}
  2670. **/
  2671. /**
  2672. The {{#crossLink "EntityManager"}}{{/crossLink}} that contains this entity.
  2673. __readOnly__
  2674. @property entityManager {EntityManager}
  2675. **/
  2676. /**
  2677. The {{#crossLink "EntityState"}}{{/crossLink}} of this entity.
  2678. __readOnly__
  2679. @property entityState {EntityState}
  2680. **/
  2681. /**
  2682. Whether this entity is in the process of being saved.
  2683. __readOnly__
  2684. @property isBeingSaved {Boolean}
  2685. **/
  2686. /**
  2687. The 'original values' of this entity where they are different from the 'current values'.
  2688. This is a map where the key is a property name and the value is the 'original value' of the property.
  2689. __readOnly__
  2690. @property originalValues {Object}
  2691. **/
  2692. /**
  2693. An {{#crossLink "Event"}}{{/crossLink}} that fires whenever a value of one of this entity's properties change.
  2694. @example
  2695. // assume order is an order entity attached to an EntityManager.
  2696. order.entityAspect.propertyChanged.subscribe(
  2697. function (propertyChangedArgs) {
  2698. // this code will be executed anytime a property value changes on the 'order' entity.
  2699. var entity = propertyChangedArgs.entity; // Note: entity === order
  2700. var propertyNameChanged = propertyChangedArgs.propertyName;
  2701. var oldValue = propertyChangedArgs.oldValue;
  2702. var newValue = propertyChangedArgs.newValue;
  2703. });
  2704. @event propertyChanged
  2705. @param entity {Entity} The entity whose property is changing.
  2706. @param propertyName {String} The property that changed. This value will be 'null' for operations that replace the entire entity. This includes
  2707. queries, imports and saves that require a merge. The remaining parameters will not exist in this case either. This will actually be a "property path"
  2708. for any properties of a complex type.
  2709. @param oldValue {Object} The old value of this property before the change.
  2710. @param newValue {Object} The new value of this property after the change.
  2711. @readOnly
  2712. **/
  2713. /**
  2714. An {{#crossLink "Event"}}{{/crossLink}} that fires whenever any of the validation errors on this entity change.
  2715. Note that this might be the removal of an error when some data on the entity is fixed.
  2716. @example
  2717. // assume order is an order entity attached to an EntityManager.
  2718. order.entityAspect.validationErrorsChanged.subscribe(
  2719. function (validationChangeArgs) {
  2720. // this code will be executed anytime a property value changes on the 'order' entity.
  2721. var entity == validationChangeArgs.entity; // Note: entity === order
  2722. var errorsAdded = validationChangeArgs.added;
  2723. var errorsCleared = validationChangeArgs.removed;
  2724. });
  2725. @event validationErrorsChanged
  2726. @param entity {Entity} The entity on which the validation errors are being added or removed.
  2727. @param added {Array of ValidationError} An array containing any newly added {{#crossLink "ValidationError"}}{{/crossLink}}s
  2728. @param removed {Array of ValidationError} An array containing any newly removed {{#crossLink "ValidationError"}}{{/crossLink}}s. This is those
  2729. errors that have been 'fixed'
  2730. @readOnly
  2731. **/
  2732. /**
  2733. Returns the {{#crossLink "EntityKey"}}{{/crossLink}} for this Entity.
  2734. @example
  2735. // assume order is an order entity attached to an EntityManager.
  2736. var entityKey = order.entityAspect.getKey();
  2737. @method getKey
  2738. @param [forceRefresh=false] {Boolean} Forces the recalculation of the key. This should normally be unnecessary.
  2739. @return {EntityKey} The {{#crossLink "EntityKey"}}{{/crossLink}} associated with this Entity.
  2740. **/
  2741. proto.getKey = function(forceRefresh) {
  2742. forceRefresh = assertParam(forceRefresh, "forceRefresh").isBoolean().isOptional().check(false);
  2743. if (forceRefresh || !this._entityKey) {
  2744. var entityType = this.entity.entityType;
  2745. var keyProps = entityType.keyProperties;
  2746. var values = keyProps.map(function(p) {
  2747. return this.entity.getProperty(p.name);
  2748. }, this);
  2749. this._entityKey = new EntityKey(entityType, values);
  2750. }
  2751. return this._entityKey;
  2752. };
  2753. /**
  2754. Returns the entity to an {{#crossLink "EntityState"}}{{/crossLink}} of 'Unchanged' by committing all changes made since the entity was last queried
  2755. had 'acceptChanges' called on it.
  2756. @example
  2757. // assume order is an order entity attached to an EntityManager.
  2758. order.entityAspect.acceptChanges();
  2759. // The 'order' entity will now be in an 'Unchanged' state with any changes committed.
  2760. @method acceptChanges
  2761. **/
  2762. proto.acceptChanges = function() {
  2763. var em = this.entityManager;
  2764. if (this.entityState.isDeleted()) {
  2765. em.detachEntity(this.entity);
  2766. } else {
  2767. this.setUnchanged();
  2768. }
  2769. em.entityChanged.publish({ entityAction: EntityAction.AcceptChanges, entity: this.entity });
  2770. };
  2771. /**
  2772. Returns the entity to an EntityState of 'Unchanged' by rejecting all changes made to it since the entity was last queried
  2773. had 'rejectChanges' called on it.
  2774. @example
  2775. // assume order is an order entity attached to an EntityManager.
  2776. order.entityAspect.rejectChanges();
  2777. // The 'order' entity will now be in an 'Unchanged' state with any changes rejected.
  2778. @method rejectChanges
  2779. **/
  2780. proto.rejectChanges = function() {
  2781. var entity = this.entity;
  2782. var entityManager = this.entityManager;
  2783. // we do not want PropertyChange or EntityChange events to occur here
  2784. __using(entityManager, "isRejectingChanges", true, function() {
  2785. rejectChangesCore(entity);
  2786. });
  2787. if (this.entityState.isAdded()) {
  2788. // next line is needed becuase the following line will cause this.entityManager -> null;
  2789. entityManager.detachEntity(entity);
  2790. // need to tell em that an entity that needed to be saved no longer does.
  2791. entityManager._notifyStateChange(entity, false);
  2792. } else {
  2793. if (this.entityState.isDeleted()) {
  2794. this.entityManager._linkRelatedEntities(entity);
  2795. }
  2796. this.setUnchanged();
  2797. // propertyChanged propertyName is null because more than one property may have changed.
  2798. this.propertyChanged.publish({ entity: entity, propertyName: null });
  2799. this.entityManager.entityChanged.publish({ entityAction: EntityAction.RejectChanges, entity: entity });
  2800. }
  2801. };
  2802. function rejectChangesCore(target) {
  2803. var aspect = target.entityAspect || target.complexAspect;
  2804. var stype = target.entityType || target.complexType;
  2805. var originalValues = aspect.originalValues;
  2806. for (var propName in originalValues) {
  2807. target.setProperty(propName, originalValues[propName]);
  2808. }
  2809. stype.complexProperties.forEach(function(cp) {
  2810. var nextTarget = target.getProperty(cp.name);
  2811. rejectChangesCore(nextTarget);
  2812. });
  2813. }
  2814. /**
  2815. Sets the entity to an EntityState of 'Unchanged'. This is also the equivalent of calling {{#crossLink "EntityAspect/acceptChanges"}}{{/crossLink}}
  2816. @example
  2817. // assume order is an order entity attached to an EntityManager.
  2818. order.entityAspect.setUnchanged();
  2819. // The 'order' entity will now be in an 'Unchanged' state with any changes committed.
  2820. @method setUnchanged
  2821. **/
  2822. proto.setUnchanged = function() {
  2823. clearOriginalValues(this.entity);
  2824. delete this.hasTempKey;
  2825. this.entityState = EntityState.Unchanged;
  2826. this.entityManager._notifyStateChange(this.entity, false);
  2827. };
  2828. function clearOriginalValues(target) {
  2829. var aspect = target.entityAspect || target.complexAspect;
  2830. aspect.originalValues = {};
  2831. var stype = target.entityType || target.complexType;
  2832. stype.complexProperties.forEach(function(cp) {
  2833. var nextTarget = target.getProperty(cp.name);
  2834. clearOriginalValues(nextTarget);
  2835. });
  2836. }
  2837. // Dangerous method - see notes - talk to Jay - this is not a complete impl
  2838. // proto.setAdded = function () {
  2839. // this.originalValues = {};
  2840. // this.entityState = EntityState.Added;
  2841. // if (this.entity.entityType.autoGeneratedKeyType !== AutoGeneratedKeyType.None) {
  2842. // this.entityManager.generateTempKeyValue(this.entity);
  2843. // }
  2844. // };
  2845. /**
  2846. Sets the entity to an EntityState of 'Modified'. This can also be achieved by changing the value of any property on an 'Unchanged' entity.
  2847. @example
  2848. // assume order is an order entity attached to an EntityManager.
  2849. order.entityAspect.setModified();
  2850. // The 'order' entity will now be in a 'Modified' state.
  2851. @method setModified
  2852. **/
  2853. proto.setModified = function() {
  2854. this.entityState = EntityState.Modified;
  2855. this.entityManager._notifyStateChange(this.entity, true);
  2856. };
  2857. /**
  2858. Sets the entity to an EntityState of 'Deleted'. This both marks the entity as being scheduled for deletion during the next 'Save' call
  2859. but also removes the entity from all of its related entities.
  2860. @example
  2861. // assume order is an order entity attached to an EntityManager.
  2862. order.entityAspect.setDeleted();
  2863. // The 'order' entity will now be in a 'Deleted' state and it will no longer have any 'related' entities.
  2864. @method setDeleted
  2865. **/
  2866. proto.setDeleted = function() {
  2867. var em = this.entityManager;
  2868. if (this.entityState.isAdded()) {
  2869. em.detachEntity(this.entity);
  2870. em._notifyStateChange(this.entity, false);
  2871. } else {
  2872. this.entityState = EntityState.Deleted;
  2873. this._removeFromRelations();
  2874. em._notifyStateChange(this.entity, true);
  2875. }
  2876. // TODO: think about cascade deletes
  2877. };
  2878. /**
  2879. Performs validation on the entity, any errors encountered during the validation are available via the
  2880. {{#crossLink "EntityAspect.getValidationErrors"}}{{/crossLink}} method. Validating an entity means executing
  2881. all of the validators on both the entity itself as well as those on each of its properties.
  2882. @example
  2883. // assume order is an order entity attached to an EntityManager.
  2884. var isOk = order.entityAspect.validateEntity();
  2885. // isOk will be 'true' if there are no errors on the entity.
  2886. if (!isOk) {
  2887. var errors = order.entityAspect.getValidationErrors();
  2888. }
  2889. @method validateEntity
  2890. @return {Boolean} Whether the entity passed validation.
  2891. **/
  2892. proto.validateEntity = function () {
  2893. var ok =true;
  2894. this._processValidationOpAndPublish(function(that) {
  2895. ok = validateTarget(that.entity);
  2896. });
  2897. return ok;
  2898. };
  2899. function validateTarget(target) {
  2900. var ok = true;
  2901. var stype = target.entityType || target.complexType;
  2902. var aspect = target.entityAspect || target.complexAspect;
  2903. var entityAspect = target.entityAspect || target.complexAspect.entityAspect;
  2904. stype.getProperties().forEach(function (p) {
  2905. var value = target.getProperty(p.name);
  2906. var propName = aspect.propertyPath ? aspect.propertyPath + "." + p.name : p.name;
  2907. if (p.validators.length > 0) {
  2908. var context = { entity: entityAspect.entity, property: p, propertyName: propName };
  2909. ok = entityAspect._validateProperty(value, context) && ok;
  2910. }
  2911. if (p.isComplexProperty) {
  2912. ok = validateTarget(value) && ok;
  2913. }
  2914. });
  2915. // then entity level
  2916. stype.validators.forEach(function (validator) {
  2917. ok = validate(entityAspect, validator, aspect.entity) && ok;
  2918. });
  2919. return ok;
  2920. }
  2921. /**
  2922. Performs validation on a specific property of this entity, any errors encountered during the validation are available via the
  2923. {{#crossLink "EntityAspect.getValidationErrors"}}{{/crossLink}} method. Validating a property means executing
  2924. all of the validators on the specified property. This call is also made automatically anytime a property
  2925. of an entity is changed.
  2926. @example
  2927. // assume order is an order entity attached to an EntityManager.
  2928. var isOk = order.entityAspect.validateProperty("Order");
  2929. or
  2930. @example
  2931. var orderDateProperty = order.entityType.getProperty("OrderDate");
  2932. var isOk = order.entityAspect.validateProperty(OrderDateProperty);
  2933. @method validateProperty
  2934. @param property {DataProperty|NavigationProperty|String} The {{#crossLink "DataProperty"}}{{/crossLink}} or
  2935. {{#crossLink "NavigationProperty"}}{{/crossLink}} to validate or a string with the name of the property or a property path with
  2936. the path to a property of a complex object.
  2937. @param [context] {Object} A context object used to pass additional information to each {{#crossLink "Validator"}}{{/crossLink}}
  2938. @return {Boolean} Whether the entity passed validation.
  2939. **/
  2940. proto.validateProperty = function (property, context) {
  2941. var value = this.getPropertyValue(property); // performs validations
  2942. if (value.complexAspect) {
  2943. return validateTarget(value);
  2944. }
  2945. context = context || {};
  2946. context.entity = this.entity;
  2947. if (typeof(property) === 'string') {
  2948. context.property = this.entity.entityType.getProperty(property, true);
  2949. context.propertyName = property;
  2950. } else {
  2951. context.property = property;
  2952. context.propertyName = property.name;
  2953. }
  2954. return this._validateProperty(value, context);
  2955. };
  2956. /**
  2957. Returns the validation errors associated with either the entire entity or any specified property.
  2958. @example
  2959. This method can return all of the errors for an Entity
  2960. @example
  2961. // assume order is an order entity attached to an EntityManager.
  2962. var valErrors = order.entityAspect.getValidationErrors();
  2963. as well as those for just a specific property.
  2964. @example
  2965. // assume order is an order entity attached to an EntityManager.
  2966. var orderDateErrors = order.entityAspect.getValidationErrors("OrderDate");
  2967. which can also be expressed as
  2968. @example
  2969. // assume order is an order entity attached to an EntityManager.
  2970. var orderDateProperty = order.entityType.getProperty("OrderDate");
  2971. var orderDateErrors = order.entityAspect.getValidationErrors(orderDateProperty);
  2972. @method getValidationErrors
  2973. @param [property] {DataProperty|NavigationProperty} The property for which validation errors should be retrieved.
  2974. If omitted, all of the validation errors for this entity will be returned.
  2975. @return {Array of ValidationError}
  2976. **/
  2977. proto.getValidationErrors = function (property) {
  2978. assertParam(property, "property").isOptional().isEntityProperty().or().isString().check();
  2979. var result = __getOwnPropertyValues(this._validationErrors);
  2980. if (property) {
  2981. var propertyName = typeof (property) === 'string' ? property : property.name;
  2982. result = result.filter(function (ve) {
  2983. return (ve.property.name === propertyName);
  2984. });
  2985. }
  2986. return result;
  2987. };
  2988. /**
  2989. Adds a validation error for a specified property.
  2990. @method addValidationError
  2991. @param validationError {ValidationError}
  2992. **/
  2993. proto.addValidationError = function (validationError) {
  2994. assertParam(validationError, "validationError").isInstanceOf(ValidationError).check();
  2995. this._processValidationOpAndPublish(function (that) {
  2996. that._addValidationError(validationError);
  2997. });
  2998. };
  2999. /**
  3000. Removes a validation error for a specified property.
  3001. @method removeValidationError
  3002. @param validator {Validator}
  3003. @param [property] {DataProperty|NavigationProperty}
  3004. **/
  3005. proto.removeValidationError = function (validator, property) {
  3006. assertParam(validator, "validator").isString().or().isInstanceOf(Validator).check();
  3007. assertParam(property, "property").isOptional().isEntityProperty().check();
  3008. this._processValidationOpAndPublish(function (that) {
  3009. that._removeValidationError(validator, property && property.name);
  3010. });
  3011. };
  3012. /**
  3013. Removes all of the validation errors for a specified entity
  3014. @method clearValidationErrors
  3015. **/
  3016. proto.clearValidationErrors = function () {
  3017. this._processValidationOpAndPublish(function (that) {
  3018. __objectForEach(that._validationErrors, function(key, valError) {
  3019. if (valError) {
  3020. delete that._validationErrors[key];
  3021. that._pendingValidationResult.removed.push(valError);
  3022. }
  3023. });
  3024. });
  3025. };
  3026. /**
  3027. Performs a query for the value of a specified {{#crossLink "NavigationProperty"}}{{/crossLink}}.
  3028. @example
  3029. emp.entityAspect.loadNavigationProperty("Orders")
  3030. .then(function (data) {
  3031. var orders = data.results;
  3032. }).fail(function (exception) {
  3033. // handle exception here;
  3034. });
  3035. @method loadNavigationProperty
  3036. @async
  3037. @param navigationProperty {NavigationProperty} The NavigationProperty to 'load'.
  3038. @param [callback] {Function} Function to call on success.
  3039. @param [errorCallback] {Function} Function to call on failure.
  3040. @return {Promise}
  3041. promiseData.results {Array of Entity}
  3042. promiseData.query {EntityQuery} The original query
  3043. promiseData.XHR {XMLHttpRequest} The raw XMLHttpRequest returned from the server.
  3044. **/
  3045. // This method is provided in entityQuery.js.
  3046. // proto.loadNavigationProperty = function(navigationProperty, callback, errorCallback)
  3047. // returns null for np's that do not have a parentKey
  3048. proto.getParentKey = function (navigationProperty) {
  3049. // NavigationProperty doesn't yet exist
  3050. // assertParam(navigationProperty, "navigationProperty").isInstanceOf(NavigationProperty).check();
  3051. var fkNames = navigationProperty.foreignKeyNames;
  3052. if (fkNames.length === 0) return null;
  3053. var that = this;
  3054. var fkValues = fkNames.map(function (fkn) {
  3055. return that.entity.getProperty(fkn);
  3056. });
  3057. return new EntityKey(navigationProperty.entityType, fkValues);
  3058. };
  3059. proto.getPropertyValue = function (property) {
  3060. assertParam(property, "property").isString().or().isEntityProperty().check();
  3061. var value;
  3062. if (typeof (property) === 'string') {
  3063. var propNames = property.trim().split(".");
  3064. var propName = propNames.shift();
  3065. value = this.entity;
  3066. value = value.getProperty(propName);
  3067. while (propNames.length > 0) {
  3068. propName = propNames.shift();
  3069. value = value.getProperty(propName);
  3070. }
  3071. } else {
  3072. if (!(property.parentType instanceof EntityType)) {
  3073. throw new Error("The validateProperty method does not accept a 'property' parameter whose parentType is a ComplexType; " +
  3074. "Pass a 'property path' string as the 'property' paramter instead ");
  3075. }
  3076. value = this.entity.getProperty(property.name);
  3077. }
  3078. return value;
  3079. };
  3080. // internal methods
  3081. proto._detach = function() {
  3082. this.entityGroup = null;
  3083. this.entityManager = null;
  3084. this.entityState = EntityState.Detached;
  3085. this.originalValues = {};
  3086. this._validationErrors = {};
  3087. this.validationErrorsChanged.clear();
  3088. this.propertyChanged.clear();
  3089. };
  3090. proto._removeFromRelations = function () {
  3091. var entity = this.entity;
  3092. // remove this entity from any collections.
  3093. // mark the entity deleted
  3094. entity.entityType.navigationProperties.forEach(function (np) {
  3095. var inverseNp = np.inverse;
  3096. if (!inverseNp) return;
  3097. var npValue = entity.getProperty(np.name);
  3098. if (np.isScalar) {
  3099. if (npValue) {
  3100. if (inverseNp.isScalar) {
  3101. npValue.setProperty(inverseNp.name, null);
  3102. } else {
  3103. var collection = npValue.getProperty(inverseNp.name);
  3104. if (collection.length) {
  3105. __arrayRemoveItem(collection, entity);
  3106. }
  3107. }
  3108. entity.setProperty(np.name, null);
  3109. }
  3110. } else {
  3111. // npValue is a live list so we need to copy it first.
  3112. npValue.slice(0).forEach(function (v) {
  3113. if (inverseNp.isScalar) {
  3114. v.setProperty(inverseNp.name, null);
  3115. } else {
  3116. // TODO: many to many - not yet handled.
  3117. }
  3118. });
  3119. // now clear it.
  3120. npValue.length = 0;
  3121. }
  3122. });
  3123. };
  3124. // called from defaultInterceptor.
  3125. proto._validateProperty = function (value, context) {
  3126. var ok = true;
  3127. this._processValidationOpAndPublish(function (that) {
  3128. context.property.validators.forEach(function (validator) {
  3129. ok = ok && validate(that, validator, value, context);
  3130. });
  3131. });
  3132. return ok;
  3133. };
  3134. proto._processValidationOpAndPublish = function (validationFn) {
  3135. if (this._pendingValidationResult) {
  3136. // only top level processValidations call publishes
  3137. validationFn(this);
  3138. } else {
  3139. try {
  3140. this._pendingValidationResult = { entity: this.entity, added: [], removed: [] };
  3141. validationFn(this);
  3142. if (this._pendingValidationResult.added.length > 0 || this._pendingValidationResult.removed.length > 0) {
  3143. this.validationErrorsChanged.publish(this._pendingValidationResult);
  3144. }
  3145. } finally {
  3146. this._pendingValidationResult = undefined;
  3147. }
  3148. }
  3149. };
  3150. proto._addValidationError = function (validationError) {
  3151. this._validationErrors[validationError.key] = validationError;
  3152. this._pendingValidationResult.added.push(validationError);
  3153. };
  3154. proto._removeValidationError = function (validator, propertyPath) {
  3155. var key = ValidationError.getKey(validator, propertyPath);
  3156. var valError = this._validationErrors[key];
  3157. if (valError) {
  3158. delete this._validationErrors[key];
  3159. this._pendingValidationResult.removed.push(valError);
  3160. }
  3161. };
  3162. function validate(aspect, validator, value, context) {
  3163. var ve = validator.validate(value, context);
  3164. if (ve) {
  3165. aspect._addValidationError(ve);
  3166. return false;
  3167. } else {
  3168. aspect._removeValidationError(validator, context ? context.propertyName: null);
  3169. return true;
  3170. }
  3171. }
  3172. return ctor;
  3173. }();
  3174. var ComplexAspect = function() {
  3175. /**
  3176. An ComplexAspect instance is associated with every complex object instance and is accessed via the complex object's 'complexAspect' property.
  3177. The ComplexAspect itself provides properties to determine the parent object, parent property and original values for the complex object.
  3178. A ComplexAspect will almost never need to be constructed directly. You will usually get an ComplexAspect by accessing
  3179. an entities 'complexAspect' property. This property will be automatically attached when an complex object is created as part of an
  3180. entity via either a query, import or EntityManager.createEntity call.
  3181. // assume address is a complex property on the 'Customer' type
  3182. var aspect = aCustomer.address.complexAspect;
  3183. // aCustomer === aspect.parent;
  3184. @class ComplexAspect
  3185. **/
  3186. var ctor = function(complexObject, parent, parentProperty) {
  3187. if (!complexObject) {
  3188. throw new Error("The ComplexAspect ctor requires an entity as its only argument.");
  3189. }
  3190. if (complexObject.complexAspect) {
  3191. return complexObject.complexAspect;
  3192. }
  3193. // if called without new
  3194. if (!(this instanceof ComplexAspect)) {
  3195. return new ComplexAspect(complexObject, parent, parentProperty);
  3196. }
  3197. // entityType should already be on the entity from 'watch'
  3198. this.complexObject = complexObject;
  3199. complexObject.complexAspect = this;
  3200. // TODO: keep public or not?
  3201. this.originalValues = {};
  3202. // if a standalone complexObject
  3203. if (parent == null) {
  3204. this.entityAspect = new EntityAspect(null);
  3205. } else {
  3206. this.parent = parent;
  3207. this.parentProperty = parentProperty;
  3208. this.propertyPath = parentProperty;
  3209. // get the final parent's entityAspect.
  3210. var nextParent = parent;
  3211. while (nextParent.complexType) {
  3212. this.propertyPath = nextParent.complexAspect.propertyPath + "." + this.propertyPath;
  3213. nextParent = nextParent.complexType.parent;
  3214. }
  3215. this.entityAspect = nextParent.entityAspect;
  3216. }
  3217. var complexType = complexObject.complexType;
  3218. if (!complexType) {
  3219. var typeName = complexObject.prototype._$typeName;
  3220. if (!typeName) {
  3221. throw new Error("This entity is not registered as a valid ComplexType");
  3222. } else {
  3223. throw new Error("Metadata for this complexType has not yet been resolved: " + typeName);
  3224. }
  3225. }
  3226. var complexCtor = complexType.getCtor();
  3227. __modelLibraryDef.getDefaultInstance().startTracking(complexObject, complexCtor.prototype);
  3228. };
  3229. var proto = ctor.prototype;
  3230. /**
  3231. The complex object that this aspect is associated with.
  3232. __readOnly__
  3233. @property complexObject {Entity}
  3234. **/
  3235. /**
  3236. The parent object that to which this aspect belongs; this will either be an entity or another complex object.
  3237. __readOnly__
  3238. @property parent {Entity|ComplexObject}
  3239. **/
  3240. /**
  3241. The {{#crossLink "DataProperty"}}{{/crossLink}} on the 'parent' that contains this complex object.
  3242. __readOnly__
  3243. @property parentProperty {DataProperty}
  3244. **/
  3245. /**
  3246. The EntityAspect for the top level entity tht contains this complex object.
  3247. __readOnly__
  3248. @property entityAspect {String}
  3249. **/
  3250. /**
  3251. The 'property path' from the top level entity that contains this complex object to this object.
  3252. __readOnly__
  3253. @property propertyPath {String}
  3254. **/
  3255. /**
  3256. The 'original values' of this complex object where they are different from the 'current values'.
  3257. This is a map where the key is a property name and the value is the 'original value' of the property.
  3258. __readOnly__
  3259. @property originalValues {Object}
  3260. **/
  3261. proto._postInitialize = function() {
  3262. var co = this.complexObject;
  3263. var aCtor = co.complexType.getCtor();
  3264. var initFn = aCtor._$initializationFn;
  3265. if (initFn) {
  3266. if (typeof initFn === "string") {
  3267. co[initFn](co);
  3268. } else {
  3269. aCtor._$initializationFn(co);
  3270. }
  3271. }
  3272. };
  3273. return ctor;
  3274. }();
  3275. var EntityKey = (function () {
  3276. var ENTITY_KEY_DELIMITER = ":::";
  3277. /**
  3278. An EntityKey is an object that represents the unique identity of an entity. EntityKey's are immutable.
  3279. @class EntityKey
  3280. **/
  3281. /**
  3282. Constructs a new EntityKey. Each entity within an EntityManager will have a unique EntityKey.
  3283. @example
  3284. // assume em1 is an EntityManager containing a number of existing entities.
  3285. var empType = em1.metadataStore.getEntityType("Employee");
  3286. var entityKey = new EntityKey(empType, 1);
  3287. EntityKey's may also be found by calling EntityAspect.getKey()
  3288. @example
  3289. // assume employee1 is an existing Employee entity
  3290. var empKey = employee1.entityAspect.getKey();
  3291. Multipart keys are created by passing an array as the 'keyValues' parameter
  3292. @example
  3293. var empTerrType = em1.metadataStore.getEntityType("EmployeeTerritory");
  3294. var empTerrKey = new EntityKey(empTerrType, [ 1, 77]);
  3295. // The order of the properties in the 'keyValues' array must be the same as that
  3296. // returned by empTerrType.keyProperties
  3297. @method <ctor> EntityKey
  3298. @param entityType {EntityType} The {{#crossLink "EntityType"}}{{/crossLink}} of the entity.
  3299. @param keyValues {value|Array of values} A single value or an array of values.
  3300. **/
  3301. var ctor = function (entityType, keyValues) {
  3302. // can't ref EntityType here because of module circularity
  3303. // assertParam(entityType, "entityType").isInstanceOf(EntityType).check();
  3304. if (!Array.isArray(keyValues)) {
  3305. keyValues = Array.prototype.slice.call(arguments, 1);
  3306. }
  3307. // fluff
  3308. //if (!(this instanceof ctor)) {
  3309. // return new ctor(entityType, keyValues);
  3310. //}
  3311. this.entityType = entityType;
  3312. this.values = keyValues;
  3313. this._keyInGroup = createKeyString(keyValues);
  3314. };
  3315. ctor._$typeName = "EntityKey";
  3316. var proto = ctor.prototype;
  3317. proto.toJSON = function () {
  3318. return {
  3319. entityType: this.entityType.name,
  3320. values: this.values
  3321. };
  3322. };
  3323. ctor.fromJSON = function (json, metadataStore) {
  3324. var et = metadataStore.getEntityType(json.entityType, true);
  3325. return new EntityKey(et, json.values);
  3326. };
  3327. /**
  3328. Used to compare EntityKeys are determine if they refer to the same Entity.
  3329. There is also an static version of 'equals' with the same functionality.
  3330. @example
  3331. // assume em1 is an EntityManager containing a number of existing entities.
  3332. var empType = em1.metadataStore.getEntityType("Employee");
  3333. var empKey1 = new EntityKey(empType, 1);
  3334. // assume employee1 is an existing Employee entity
  3335. var empKey2 = employee1.entityAspect.getKey();
  3336. if (empKey1.equals(empKey2)) {
  3337. // do something ...
  3338. }
  3339. @method equals
  3340. @param entityKey {EntityKey}
  3341. **/
  3342. proto.equals = function (entityKey) {
  3343. if (!(entityKey instanceof EntityKey)) return false;
  3344. return (this.entityType === entityKey.entityType) &&
  3345. __arrayEquals(this.values, entityKey.values);
  3346. };
  3347. /*
  3348. Returns a human readable representation of this EntityKey.
  3349. @method toString
  3350. */
  3351. proto.toString = function () {
  3352. return this.entityType.name + '-' + this._keyInGroup;
  3353. };
  3354. /**
  3355. Used to compare EntityKeys are determine if they refer to the same Entity.
  3356. There is also an instance version of 'equals' with the same functionality.
  3357. @example
  3358. // assume em1 is an EntityManager containing a number of existing entities.
  3359. var empType = em1.metadataStore.getEntityType("Employee");
  3360. var empKey1 = new EntityKey(empType, 1);
  3361. // assume employee1 is an existing Employee entity
  3362. var empKey2 = employee1.entityAspect.getKey();
  3363. if (EntityKey.equals(empKey1, empKey2)) {
  3364. // do something ...
  3365. }
  3366. @method equals
  3367. @static
  3368. @param k1 {EntityKey}
  3369. @param k2 {EntityKey}
  3370. **/
  3371. ctor.equals = function (k1, k2) {
  3372. if (!(k1 instanceof EntityKey)) return false;
  3373. return k1.equals(k2);
  3374. };
  3375. // TODO: we may want to compare to default values later.
  3376. proto._isEmpty = function () {
  3377. return this.values.join("").length === 0;
  3378. };
  3379. ctor._fromRawEntity = function (rawEntity, entityType) {
  3380. var keyValues = entityType.keyProperties.map(function (p) {
  3381. return rawEntity[p.nameOnServer];
  3382. });
  3383. return new EntityKey(entityType, keyValues);
  3384. };
  3385. function createKeyString(keyValues) {
  3386. return keyValues.join(ENTITY_KEY_DELIMITER);
  3387. }
  3388. return ctor;
  3389. })();
  3390. breeze.EntityAspect= EntityAspect;
  3391. breeze.ComplexAspect= ComplexAspect;
  3392. breeze.EntityState= EntityState;
  3393. breeze.EntityAction= EntityAction;
  3394. breeze.EntityKey = EntityKey;
  3395. function defaultPropertyInterceptor(property, newValue, rawAccessorFn) {
  3396. // 'this' is the entity itself in this context.
  3397. var oldValue = rawAccessorFn();
  3398. var dataType = property.dataType;
  3399. if (dataType && dataType.parse) {
  3400. // attempts to coerce a value to the correct type - if this fails return the value unchanged
  3401. newValue = dataType.parse(newValue, typeof newValue);
  3402. }
  3403. // exit if no change
  3404. if (newValue === oldValue) {
  3405. return;
  3406. }
  3407. var that = this;
  3408. // need 2 propNames here because of complexTypes;
  3409. var propName = property.name;
  3410. var propPath;
  3411. // CANNOT DO NEXT LINE because it has the possibility of creating a new property
  3412. // 'entityAspect' on 'this'. - Not permitted by IE inside of a defined property on a prototype.
  3413. // var entityAspect = new EntityAspect(this);
  3414. var entityAspect = this.entityAspect;
  3415. var localAspect;
  3416. if (entityAspect) {
  3417. localAspect = entityAspect;
  3418. propPath = propName;
  3419. } else {
  3420. localAspect = this.complexAspect;
  3421. entityAspect = localAspect.entityAspect;
  3422. // if complexType is standalone - i.e. doesn't have a pareent - don't try to calc a fullPropName;
  3423. propPath = (localAspect.parent) ?
  3424. localAspect.propertyPath + "." + propName :
  3425. propName;
  3426. }
  3427. // Note that we need to handle multiple properties in process. not just one
  3428. // NOTE: this may not be needed because of the newValue === oldValue test above.
  3429. // to avoid recursion. ( except in the case of null propagation with fks where null -> 0 in some cases.)
  3430. var inProcess = entityAspect._inProcess;
  3431. if (inProcess) {
  3432. // check for recursion
  3433. if (inProcess.indexOf(property) >= 0) return;
  3434. inProcess.push(property);
  3435. } else {
  3436. inProcess = [property];
  3437. entityAspect._inProcess = inProcess;
  3438. }
  3439. // entityAspect.entity used because of complexTypes
  3440. // 'this' != entity when 'this' is a complexObject; in that case this: complexObject and entity: entity
  3441. var entity = entityAspect.entity;
  3442. // We could use __using here but decided not to for perf reasons - this method runs a lot.
  3443. // i.e __using(entityAspect, "_inProcess", property, function() {...
  3444. try {
  3445. var entityManager = entityAspect.entityManager;
  3446. // store an original value for this property if not already set
  3447. if (entityAspect.entityState.isUnchangedOrModified()) {
  3448. if (localAspect.originalValues[propName]===undefined && property.isDataProperty && !property.isComplexProperty) {
  3449. // otherwise this entry will be skipped during serialization
  3450. localAspect.originalValues[propName] = oldValue !== undefined ? oldValue : property.defaultValue;
  3451. }
  3452. }
  3453. // set the value
  3454. if (property.isNavigationProperty) {
  3455. if (!property.isScalar) {
  3456. throw new Error("Nonscalar navigation properties are readonly - entities can be added or removed but the collection may not be changed.");
  3457. }
  3458. var inverseProp = property.inverse;
  3459. var oldSiblings;
  3460. if (newValue) {
  3461. if (entityManager) {
  3462. if (newValue.entityAspect.entityState.isDetached()) {
  3463. if (!entityManager.isLoading) {
  3464. entityManager.attachEntity(newValue, EntityState.Added);
  3465. }
  3466. } else {
  3467. if (newValue.entityAspect.entityManager !== entityManager) {
  3468. throw new Error("An Entity cannot be attached to an entity in another EntityManager. One of the two entities must be detached first.");
  3469. }
  3470. }
  3471. } else {
  3472. if (newValue.entityAspect && newValue.entityAspect.entityManager) {
  3473. entityManager = newValue.entityAspect.entityManager;
  3474. var newState = entityManager.isLoading ? EntityState.Unchanged : EntityState.Added;
  3475. entityManager.attachEntity(entityAspect.entity, newState);
  3476. }
  3477. }
  3478. // process related updates ( the inverse relationship) first so that collection dups check works properly.
  3479. // update inverse relationship
  3480. if (inverseProp) {
  3481. if (inverseProp.isScalar) {
  3482. // navigation property change - undo old relation
  3483. if (oldValue) {
  3484. // TODO: null -> NullEntity later
  3485. oldValue.setProperty(inverseProp.name, null);
  3486. }
  3487. newValue.setProperty(inverseProp.name, this);
  3488. } else {
  3489. // navigation property change - undo old relation
  3490. if (oldValue) {
  3491. oldSiblings = oldValue.getProperty(inverseProp.name);
  3492. var ix = oldSiblings.indexOf(this);
  3493. if (ix !== -1) {
  3494. oldSiblings.splice(ix, 1);
  3495. }
  3496. }
  3497. var siblings = newValue.getProperty(inverseProp.name);
  3498. // recursion check if already in the collection is performed by the relationArray
  3499. siblings.push(this);
  3500. }
  3501. }
  3502. } else {
  3503. // To get here - the newValue is either null or undefined;
  3504. if (inverseProp) {
  3505. if (inverseProp.isScalar) {
  3506. // navigation property change - undo old relation
  3507. if (oldValue) {
  3508. // TODO: null -> NullEntity later
  3509. oldValue.setProperty(inverseProp.name, null);
  3510. }
  3511. } else {
  3512. // navigation property change - undo old relation
  3513. if (oldValue) {
  3514. oldSiblings = oldValue.getProperty(inverseProp.name);
  3515. var ix = oldSiblings.indexOf(this);
  3516. if (ix !== -1) {
  3517. oldSiblings.splice(ix, 1);
  3518. }
  3519. }
  3520. }
  3521. }
  3522. }
  3523. rawAccessorFn(newValue);
  3524. if (entityManager && !entityManager.isLoading) {
  3525. if (entityAspect.entityState.isUnchanged() && !property.isUnmapped) {
  3526. entityAspect.setModified();
  3527. }
  3528. if (entityManager.validationOptions.validateOnPropertyChange) {
  3529. entityAspect._validateProperty(newValue,
  3530. { entity: this, property: property, propertyName: propPath, oldValue: oldValue });
  3531. }
  3532. }
  3533. // update fk data property
  3534. if (property.relatedDataProperties) {
  3535. if (!entityAspect.entityState.isDeleted()) {
  3536. var inverseKeyProps = property.entityType.keyProperties;
  3537. inverseKeyProps.forEach(function(keyProp, i ) {
  3538. var relatedValue = newValue ? newValue.getProperty(keyProp.name) : keyProp.defaultValue;
  3539. that.setProperty(property.relatedDataProperties[i].name, relatedValue);
  3540. });
  3541. }
  3542. }
  3543. } else if (property.isComplexProperty) {
  3544. if (!newValue) {
  3545. throw new Error(__formatString("You cannot set the '%1' property to null because it's datatype is the ComplexType: '%2'", property.name, property.dataType.name));
  3546. }
  3547. // To get here it must be a ComplexProperty
  3548. // 'dataType' will be a complexType
  3549. if (!oldValue) {
  3550. var ctor = dataType.getCtor();
  3551. oldValue = new ctor();
  3552. rawAccessorFn(oldValue);
  3553. }
  3554. dataType.dataProperties.forEach(function(dp) {
  3555. var pn = dp.name;
  3556. var nv = newValue.getProperty(pn);
  3557. oldValue.setProperty(pn, nv);
  3558. });
  3559. } else {
  3560. // To get here it must be a (nonComplex) DataProperty
  3561. if (property.isPartOfKey && entityManager && !entityManager.isLoading) {
  3562. var keyProps = this.entityType.keyProperties;
  3563. var values = keyProps.map(function(p) {
  3564. if (p == property) {
  3565. return newValue;
  3566. } else {
  3567. return this.getProperty(p.name);
  3568. }
  3569. }, this);
  3570. var newKey = new EntityKey(this.entityType, values);
  3571. if (entityManager.findEntityByKey(newKey)) {
  3572. throw new Error("An entity with this key is already in the cache: " + newKey.toString());
  3573. }
  3574. var oldKey = this.entityAspect.getKey();
  3575. var eg = entityManager.findEntityGroup(this.entityType);
  3576. eg._replaceKey(oldKey, newKey);
  3577. }
  3578. rawAccessorFn(newValue);
  3579. // NOTE: next few lines are the same as above but not refactored for perf reasons.
  3580. if (entityManager && !entityManager.isLoading) {
  3581. if (entityAspect.entityState.isUnchanged() && !property.isUnmapped) {
  3582. entityAspect.setModified();
  3583. }
  3584. if (entityManager.validationOptions.validateOnPropertyChange) {
  3585. entityAspect._validateProperty(newValue,
  3586. { entity: entity, property: property, propertyName: propPath, oldValue: oldValue });
  3587. }
  3588. }
  3589. // update corresponding nav property if attached.
  3590. if (property.relatedNavigationProperty && entityManager) {
  3591. var relatedNavProp = property.relatedNavigationProperty;
  3592. if (newValue) {
  3593. var key = new EntityKey(relatedNavProp.entityType, [newValue]);
  3594. var relatedEntity = entityManager.findEntityByKey(key);
  3595. if (relatedEntity) {
  3596. this.setProperty(relatedNavProp.name, relatedEntity);
  3597. } else {
  3598. // it may not have been fetched yet in which case we want to add it as an unattachedChild.
  3599. entityManager._unattachedChildrenMap.addChild(key, relatedNavProp, this);
  3600. }
  3601. } else {
  3602. this.setProperty(relatedNavProp.name, null);
  3603. }
  3604. }
  3605. if (property.isPartOfKey) {
  3606. // propogate pk change to all related entities;
  3607. if (oldValue && !entityAspect.entityState.isDetached()) {
  3608. entityAspect.primaryKeyWasChanged = true;
  3609. }
  3610. this.entityType.navigationProperties.forEach(function(np) {
  3611. var inverseNp = np.inverse;
  3612. if (!inverseNp) return;
  3613. if (inverseNp.foreignKeyNames.length === 0) return;
  3614. var npValue = that.getProperty(np.name);
  3615. var propertyIx = that.entityType.keyProperties.indexOf(property);
  3616. var fkName = inverseNp.foreignKeyNames[propertyIx];
  3617. if (np.isScalar) {
  3618. if (!npValue) return;
  3619. npValue.setProperty(fkName, newValue);
  3620. } else {
  3621. npValue.forEach(function(iv) {
  3622. iv.setProperty(fkName, newValue);
  3623. });
  3624. }
  3625. });
  3626. // insure that cached key is updated.
  3627. entityAspect.getKey(true);
  3628. }
  3629. }
  3630. var propChangedArgs = { entity: entity, property: property, propertyName: propPath, oldValue: oldValue, newValue: newValue };
  3631. if (entityManager) {
  3632. // propertyChanged will be fired during loading but we only want to fire it once per entity, not once per property.
  3633. // so propertyChanged is fired in the entityManager mergeEntity method if not fired here.
  3634. if ( (!entityManager.isLoading) && (!entityManager.isRejectingChanges)) {
  3635. entityAspect.propertyChanged.publish(propChangedArgs);
  3636. // don't fire entityChanged event if propertyChanged is suppressed.
  3637. entityManager.entityChanged.publish({ entityAction: EntityAction.PropertyChange, entity: entity, args: propChangedArgs });
  3638. }
  3639. } else {
  3640. entityAspect.propertyChanged.publish(propChangedArgs);
  3641. }
  3642. } finally {
  3643. inProcess.pop();
  3644. }
  3645. }
  3646. /**
  3647. @module breeze
  3648. **/
  3649. var Q = __requireLib("Q", "See https://github.com/kriskowal/q ");
  3650. // TODO: still need to handle inheritence here.
  3651. var LocalQueryComparisonOptions = (function () {
  3652. /**
  3653. A LocalQueryComparisonOptions instance is used to specify the "comparison rules" used when performing "local queries" in order
  3654. to match the semantics of these same queries when executed against a remote service. These options should be set based on the
  3655. manner in which your remote service interprets certain comparison operations.
  3656. The default LocalQueryComparisonOptions stipulates 'caseInsensitive" queries with ANSI SQL rules regarding comparisons of unequal
  3657. length strings.
  3658. @class LocalQueryComparisonOptions
  3659. **/
  3660. /**
  3661. LocalQueryComparisonOptions constructor
  3662. @example
  3663. // create a 'caseSensitive - non SQL' instance.
  3664. var lqco = new LocalQueryComparisonOptions({
  3665. name: "caseSensitive-nonSQL"
  3666. isCaseSensitive: true;
  3667. usesSql92CompliantStringComparison: false;
  3668. });
  3669. // either apply it globally
  3670. lqco.setAsDefault();
  3671. // or to a specific MetadataStore
  3672. var ms = new MetadataStore({ localQueryComparisonOptions: lqco });
  3673. var em = new EntityManager( { metadataStore: ms });
  3674. @method <ctor> LocalQueryComparisonOptions
  3675. @param config {Object}
  3676. @param [config.name] {String}
  3677. @param [config.isCaseSensitive] {Boolean} Whether predicates that involve strings will be interpreted in a "caseSensitive" manner. Default is 'false'
  3678. @param [config.usesSql92CompliantStringComparison] {Boolean} Whether of not to enforce the ANSI SQL standard
  3679. of padding strings of unequal lengths before comparison with spaces. Note that per the standard, padding only occurs with equality and
  3680. inequality predicates, and not with operations like 'startsWith', 'endsWith' or 'contains'. Default is true.
  3681. **/
  3682. var ctor = function (config) {
  3683. assertConfig(config || {})
  3684. .whereParam("name").isOptional().isString()
  3685. .whereParam("isCaseSensitive").isOptional().isBoolean().withDefault(false)
  3686. .whereParam("usesSql92CompliantStringComparison").isBoolean().withDefault(true)
  3687. .applyAll(this);
  3688. if (!this.name) {
  3689. this.name = __getUuid();
  3690. }
  3691. __config._storeObject(this, proto._$typeName, this.name);
  3692. };
  3693. var proto = ctor.prototype;
  3694. proto._$typeName = "LocalQueryComparisonOptions";
  3695. //
  3696. /**
  3697. Case insensitive SQL compliant options - this is also the default unless otherwise changed.
  3698. @property caseInsensitiveSQL {LocalQueryComparisonOptions}
  3699. @static
  3700. **/
  3701. ctor.caseInsensitiveSQL = new ctor({
  3702. name: "caseInsensitiveSQL",
  3703. isCaseSensitive: false,
  3704. usesSql92CompliantStringComparison: true
  3705. });
  3706. /**
  3707. The default value whenever LocalQueryComparisonOptions are not specified. By default this is 'caseInsensitiveSQL'.
  3708. @property defaultInstance {LocalQueryComparisonOptions}
  3709. @static
  3710. **/
  3711. ctor.defaultInstance = ctor.caseInsensitiveSQL;
  3712. /**
  3713. Makes this instance the default instance.
  3714. @method setAsDefault
  3715. @example
  3716. var lqco = new LocalQueryComparisonOptions({
  3717. isCaseSensitive: false;
  3718. usesSql92CompliantStringComparison: true;
  3719. });
  3720. lqco.setAsDefault();
  3721. @chainable
  3722. **/
  3723. proto.setAsDefault = function () {
  3724. ctor.defaultInstance = this;
  3725. return this;
  3726. };
  3727. return ctor;
  3728. })();
  3729. var NamingConvention = (function () {
  3730. /**
  3731. A NamingConvention instance is used to specify the naming conventions under which a MetadataStore
  3732. will translate property names between the server and the javascript client.
  3733. The default NamingConvention does not perform any translation, it simply passes property names thru unchanged.
  3734. @class NamingConvention
  3735. **/
  3736. /**
  3737. NamingConvention constructor
  3738. @example
  3739. // A naming convention that converts the first character of every property name to uppercase on the server
  3740. // and lowercase on the client.
  3741. var namingConv = new NamingConvention({
  3742. serverPropertyNameToClient: function(serverPropertyName) {
  3743. return serverPropertyName.substr(0, 1).toLowerCase() + serverPropertyName.substr(1);
  3744. },
  3745. clientPropertyNameToServer: function(clientPropertyName) {
  3746. return clientPropertyName.substr(0, 1).toUpperCase() + clientPropertyName.substr(1);
  3747. }
  3748. });
  3749. var ms = new MetadataStore({ namingConvention: namingConv });
  3750. var em = new EntityManager( { metadataStore: ms });
  3751. @method <ctor> NamingConvention
  3752. @param config {Object}
  3753. @param config.serverPropertyNameToClient {Function} Function that takes a server property name add converts it into a client side property name.
  3754. @param config.clientPropertyNameToServer {Function} Function that takes a client property name add converts it into a server side property name.
  3755. **/
  3756. var ctor = function(config) {
  3757. assertConfig(config || {})
  3758. .whereParam("name").isOptional().isString()
  3759. .whereParam("serverPropertyNameToClient").isFunction()
  3760. .whereParam("clientPropertyNameToServer").isFunction()
  3761. .applyAll(this);
  3762. if (!this.name) {
  3763. this.name = __getUuid();
  3764. }
  3765. __config._storeObject(this, proto._$typeName, this.name);
  3766. };
  3767. var proto = ctor.prototype;
  3768. proto._$typeName = "NamingConvention";
  3769. /**
  3770. The function used to convert server side property names to client side property names.
  3771. @method serverPropertyNameToClient
  3772. @param serverPropertyName {String}
  3773. @param [property] {DataProperty|NavigationProperty} The actual DataProperty or NavigationProperty corresponding to the property name.
  3774. @return {String} The client side property name.
  3775. **/
  3776. /**
  3777. The function used to convert client side property names to server side property names.
  3778. @method clientPropertyNameToServer
  3779. @param clientPropertyName {String}
  3780. @param [property] {DataProperty|NavigationProperty} The actual DataProperty or NavigationProperty corresponding to the property name.
  3781. @return {String} The server side property name.
  3782. **/
  3783. /**
  3784. A noop naming convention - This is the default unless another is specified.
  3785. @property none {NamingConvention}
  3786. @static
  3787. **/
  3788. ctor.none = new ctor({
  3789. name: "noChange",
  3790. serverPropertyNameToClient: function(serverPropertyName) {
  3791. return serverPropertyName;
  3792. },
  3793. clientPropertyNameToServer: function(clientPropertyName) {
  3794. return clientPropertyName;
  3795. }
  3796. });
  3797. /**
  3798. The "camelCase" naming convention - This implementation only lowercases the first character of the server property name
  3799. but leaves the rest of the property name intact. If a more complicated version is needed then one should be created via the ctor.
  3800. @property camelCase {NamingConvention}
  3801. @static
  3802. **/
  3803. ctor.camelCase = new ctor({
  3804. name: "camelCase",
  3805. serverPropertyNameToClient: function (serverPropertyName) {
  3806. return serverPropertyName.substr(0, 1).toLowerCase() + serverPropertyName.substr(1);
  3807. },
  3808. clientPropertyNameToServer: function (clientPropertyName) {
  3809. return clientPropertyName.substr(0, 1).toUpperCase() + clientPropertyName.substr(1);
  3810. }
  3811. });
  3812. /**
  3813. The default value whenever NamingConventions are not specified.
  3814. @property defaultInstance {NamingConvention}
  3815. @static
  3816. **/
  3817. ctor.defaultInstance = ctor.none;
  3818. /**
  3819. Makes this instance the default instance.
  3820. @method setAsDefault
  3821. @example
  3822. var namingConv = new NamingConvention({
  3823. serverPropertyNameToClient: function(serverPropertyName) {
  3824. return serverPropertyName.substr(0, 1).toLowerCase() + serverPropertyName.substr(1);
  3825. },
  3826. clientPropertyNameToServer: function(clientPropertyName) {
  3827. return clientPropertyName.substr(0, 1).toUpperCase() + clientPropertyName.substr(1);
  3828. }
  3829. });
  3830. namingConv.setAsDefault();
  3831. @chainable
  3832. **/
  3833. proto.setAsDefault = function() {
  3834. ctor.defaultInstance = this;
  3835. return this;
  3836. };
  3837. return ctor;
  3838. })();
  3839. var MetadataStore = (function () {
  3840. /**
  3841. An instance of the MetadataStore contains all of the metadata about a collection of {{#crossLink "EntityType"}}{{/crossLink}}'s.
  3842. MetadataStores may be shared across {{#crossLink "EntityManager"}}{{/crossLink}}'s. If an EntityManager is created without an
  3843. explicit MetadataStore, the MetadataStore from the MetadataStore.defaultInstance property will be used.
  3844. @class MetadataStore
  3845. **/
  3846. var __id = 0;
  3847. /**
  3848. Constructs a new MetadataStore.
  3849. @example
  3850. var ms = new MetadataStore();
  3851. The store can then be associated with an EntityManager
  3852. @example
  3853. var entityManager = new EntityManager( {
  3854. serviceName: "api/NorthwindIBModel",
  3855. metadataStore: ms
  3856. });
  3857. or for an existing EntityManager
  3858. @example
  3859. // Assume em1 is an existing EntityManager
  3860. em1.setProperties( { metadataStore: ms });
  3861. @method <ctor> MetadataStore
  3862. @param [config] {Object} Configuration settings .
  3863. @param [config.namingConvention=NamingConvention.defaultInstance] {NamingConvention} NamingConvention to be used in mapping property names
  3864. between client and server. Uses the NamingConvention.defaultInstance if not specified.
  3865. @param [config.localQueryComparisonOptions=LocalQueryComparisonOptions.defaultInstance] {LocalQueryComparisonOptions} The LocalQueryComparisonOptions to be
  3866. used when performing "local queries" in order to match the semantics of queries against a remote service.
  3867. **/
  3868. var ctor = function (config) {
  3869. config = config || { };
  3870. assertConfig(config)
  3871. .whereParam("namingConvention").isOptional().isInstanceOf(NamingConvention).withDefault(NamingConvention.defaultInstance)
  3872. .whereParam("localQueryComparisonOptions").isOptional().isInstanceOf(LocalQueryComparisonOptions).withDefault(LocalQueryComparisonOptions.defaultInstance)
  3873. .applyAll(this);
  3874. this.dataServices = []; // array of dataServices;
  3875. this._resourceEntityTypeMap = {}; // key is resource name - value is qualified entityType name
  3876. this._entityTypeResourceMap = {}; // key is qualified entitytype name - value is resourceName
  3877. this._structuralTypeMap = {}; // key is qualified structuraltype name - value is structuralType. ( structural = entityType or complexType).
  3878. this._shortNameMap = {}; // key is shortName, value is qualified name
  3879. this._id = __id++;
  3880. this._typeRegistry = {};
  3881. this._incompleteTypeMap = {}; // key is entityTypeName; value is map where key is assocName and value is navProp
  3882. };
  3883. var proto = ctor.prototype;
  3884. proto._$typeName = "MetadataStore";
  3885. ctor.ANONTYPE_PREFIX = "_IB_";
  3886. /**
  3887. Adds a DataService to this MetadataStore. If a DataService with the same serviceName is already
  3888. in the MetadataStore an exception will be thrown.
  3889. @method addDataService
  3890. @param dataService {DataService} The DataService to add
  3891. **/
  3892. proto.addDataService = function(dataService) {
  3893. assertParam(dataService, "dataService").isInstanceOf(DataService).check();
  3894. var alreadyExists = this.dataServices.some(function(ds) {
  3895. return dataService.serviceName === ds.serviceName;
  3896. });
  3897. if (alreadyExists) {
  3898. throw new Error("A dataService with this name '" + dataService.serviceName + "' already exists in this MetadataStore");
  3899. }
  3900. this.dataServices.push(dataService);
  3901. };
  3902. /**
  3903. Adds an EntityType to this MetadataStore. No additional properties may be added to the EntityType after its has
  3904. been added to the MetadataStore.
  3905. @method addEntityType
  3906. @param structuralType {EntityType|ComplexType} The EntityType or ComplexType to add
  3907. **/
  3908. proto.addEntityType = function (structuralType) {
  3909. if (this.getEntityType(structuralType.name, true)) {
  3910. var xxx = 7;
  3911. }
  3912. structuralType.metadataStore = this;
  3913. // don't register anon types
  3914. if (!structuralType.isAnonymous) {
  3915. this._structuralTypeMap[structuralType.name] = structuralType;
  3916. this._shortNameMap[structuralType.shortName] = structuralType.name;
  3917. // in case resourceName was registered before this point
  3918. if (structuralType instanceof EntityType) {
  3919. var resourceName = this._entityTypeResourceMap[structuralType.name];
  3920. if (resourceName) {
  3921. structuralType.defaultResourceName = resourceName;
  3922. }
  3923. }
  3924. }
  3925. structuralType._fixup();
  3926. structuralType.getProperties().forEach(function (property) {
  3927. if (!property.isUnmapped) {
  3928. structuralType._mappedPropertiesCount++;
  3929. }
  3930. });
  3931. };
  3932. /**
  3933. The {{#crossLink "NamingConvention"}}{{/crossLink}} associated with this MetadataStore.
  3934. __readOnly__
  3935. @property namingConvention {NamingConvention}
  3936. **/
  3937. /**
  3938. Exports this MetadataStore to a serialized string appropriate for local storage. This operation is also called
  3939. internally when exporting an EntityManager.
  3940. @example
  3941. // assume ms is a previously created MetadataStore
  3942. var metadataAsString = ms.exportMetadata();
  3943. window.localStorage.setItem("metadata", metadataAsString);
  3944. // and later, usually in a different session imported
  3945. var metadataFromStorage = window.localStorage.getItem("metadata");
  3946. var newMetadataStore = new MetadataStore();
  3947. newMetadataStore.importMetadata(metadataFromStorage);
  3948. @method exportMetadata
  3949. @return {String} A serialized version of this MetadataStore that may be stored locally and later restored.
  3950. **/
  3951. proto.exportMetadata = function () {
  3952. var result = JSON.stringify(this, function (key, value) {
  3953. if (key === "metadataStore") return null;
  3954. if (key === "adapterInstance") return null;
  3955. if (key === "namingConvention" || key === "localQueryComparisonOptions") {
  3956. return value.name;
  3957. }
  3958. return value;
  3959. }, __config.stringifyPad);
  3960. return result;
  3961. };
  3962. /**
  3963. Imports a previously exported serialized MetadataStore into this MetadataStore.
  3964. @example
  3965. // assume ms is a previously created MetadataStore
  3966. var metadataAsString = ms.exportMetadata();
  3967. window.localStorage.setItem("metadata", metadataAsString);
  3968. // and later, usually in a different session
  3969. var metadataFromStorage = window.localStorage.getItem("metadata");
  3970. var newMetadataStore = new MetadataStore();
  3971. newMetadataStore.importMetadata(metadataFromStorage);
  3972. @method importMetadata
  3973. @param exportedString {String} A previously exported MetadataStore.
  3974. @return {MetadataStore} This MetadataStore.
  3975. @chainable
  3976. **/
  3977. proto.importMetadata = function (exportedString) {
  3978. var json = JSON.parse(exportedString);
  3979. var ncName = json.namingConvention;
  3980. var lqcoName = json.localQueryComparisonOptions;
  3981. delete json.namingConvention;
  3982. delete json.localQueryComparisonOptions;
  3983. if (this.isEmpty()) {
  3984. this.namingConvention = __config._fetchObject(NamingConvention, ncName);
  3985. this.localQueryComparisonOptions = __config._fetchObject(LocalQueryComparisonOptions, lqcoName);
  3986. } else {
  3987. if (this.namingConvention.name !== ncName) {
  3988. throw new Error("Cannot import metadata with a different 'namingConvention' from the current MetadataStore");
  3989. }
  3990. if (this.localQueryComparisonOptions.name !== lqcoName) {
  3991. throw new Error("Cannot import metadata with different 'localQueryComparisonOptions' from the current MetadataStore");
  3992. }
  3993. }
  3994. var structuralTypeMap = {};
  3995. var that = this;
  3996. __objectForEach(json._structuralTypeMap, function (key, value) {
  3997. structuralTypeMap[key] = that._structuralTypeFromJson(value);
  3998. checkTypeRegistry(that, structuralTypeMap[key]);
  3999. });
  4000. // TODO: don't think that this next line is needed
  4001. json._structuralTypeMap = structuralTypeMap;
  4002. __extend(this, json);
  4003. return this;
  4004. };
  4005. /**
  4006. Creates a new MetadataStore from a previously exported serialized MetadataStore
  4007. @example
  4008. // assume ms is a previously created MetadataStore
  4009. var metadataAsString = ms.exportMetadata();
  4010. window.localStorage.setItem("metadata", metadataAsString);
  4011. // and later, usually in a different session
  4012. var metadataFromStorage = window.localStorage.getItem("metadata");
  4013. var newMetadataStore = MetadataStore.importMetadata(metadataFromStorage);
  4014. @method importMetadata
  4015. @static
  4016. @param exportedString {String} A previously exported MetadataStore.
  4017. @return {MetadataStore} A new MetadataStore.
  4018. **/
  4019. ctor.importMetadata = function(exportedString) {
  4020. var ms = new MetadataStore();
  4021. ms.importMetadata(exportedString);
  4022. return ms;
  4023. };
  4024. /**
  4025. Returns whether Metadata has been retrieved for a specified service name.
  4026. @example
  4027. // Assume em1 is an existing EntityManager.
  4028. if (!em1.metadataStore.hasMetadataFor("api/NorthwindIBModel"))) {
  4029. // do something interesting
  4030. }
  4031. @method hasMetadataFor
  4032. @param serviceName {String} The service name.
  4033. @return {Boolean}
  4034. **/
  4035. proto.hasMetadataFor = function(serviceName) {
  4036. return !!this.getDataService(serviceName);
  4037. };
  4038. /**
  4039. Returns the DataService for a specified service name
  4040. @example
  4041. // Assume em1 is an existing EntityManager.
  4042. var ds = em1.metadataStore.getDataService("api/NorthwindIBModel");
  4043. var adapterName = ds.adapterName; // may be null
  4044. @method getDataService
  4045. @param serviceName {String} The service name.
  4046. @return {Boolean}
  4047. **/
  4048. proto.getDataService = function (serviceName) {
  4049. assertParam(serviceName, "serviceName").isString().check();
  4050. serviceName = DataService._normalizeServiceName(serviceName);
  4051. return __arrayFirst(this.dataServices, function (ds) {
  4052. return ds.serviceName === serviceName;
  4053. });
  4054. };
  4055. /**
  4056. Fetches the metadata for a specified 'service'. This method is automatically called
  4057. internally by an EntityManager before its first query against a new service.
  4058. @example
  4059. Usually you will not actually process the results of a fetchMetadata call directly, but will instead
  4060. ask for the metadata from the EntityManager after the fetchMetadata call returns.
  4061. @example
  4062. var ms = new MetadataStore();
  4063. // or more commonly
  4064. // var ms = anEntityManager.metadataStore;
  4065. ms.fetchMetadata("api/NorthwindIBModel")
  4066. .then(function(rawMetadata) {
  4067. // do something with the metadata
  4068. }
  4069. .fail(function(exception) {
  4070. // handle exception here
  4071. };
  4072. @method fetchMetadata
  4073. @async
  4074. @param dataService {DataService|String} Either a DataService or just the name of the DataService to fetch metadata for.
  4075. @param [callback] {Function} Function called on success.
  4076. successFunction([data])
  4077. @param [callback.data] {rawMetadata}
  4078. @param [errorCallback] {Function} Function called on failure.
  4079. failureFunction([error])
  4080. @param [errorCallback.error] {Error} Any error that occured wrapped into an Error object.
  4081. @return {Promise} Promise
  4082. **/
  4083. proto.fetchMetadata = function (dataService, callback, errorCallback) {
  4084. assertParam(dataService, "dataService").isString().or().isInstanceOf(DataService).check();
  4085. assertParam(callback, "callback").isFunction().isOptional().check();
  4086. assertParam(errorCallback, "errorCallback").isFunction().isOptional().check();
  4087. if (typeof dataService === "string") {
  4088. // use the dataService with a matching name or create a new one.
  4089. dataService = this.getDataService(dataService) || new DataService({ serviceName: dataService });
  4090. }
  4091. if (this.hasMetadataFor(dataService.serviceName)) {
  4092. throw new Error("Metadata for a specific serviceName may only be fetched once per MetadataStore. ServiceName: " + dataService.serviceName);
  4093. }
  4094. var deferred = Q.defer();
  4095. dataService.adapterInstance.fetchMetadata(this, dataService, deferred.resolve, deferred.reject);
  4096. return deferred.promise.then(function (rawMetadata) {
  4097. if (callback) callback(rawMetadata);
  4098. return Q.resolve(rawMetadata);
  4099. }, function (error) {
  4100. if (errorCallback) errorCallback(error);
  4101. return Q.reject(error);
  4102. });
  4103. };
  4104. /**
  4105. Used to register a constructor for an EntityType that is not known via standard Metadata discovery;
  4106. i.e. an unmapped type.
  4107. @method trackUnmappedType
  4108. @param entityCtor {Function} The constructor for the 'unmapped' type.
  4109. @param [interceptor] {Function} A function
  4110. **/
  4111. proto.trackUnmappedType = function (entityCtor, interceptor) {
  4112. assertParam(entityCtor, "entityCtor").isFunction().check();
  4113. assertParam(interceptor, "interceptor").isFunction().isOptional().check();
  4114. // TODO: think about adding this to the MetadataStore.
  4115. var entityType = new EntityType(this);
  4116. entityType._setCtor(entityCtor, interceptor);
  4117. };
  4118. /**
  4119. Provides a mechanism to register a 'custom' constructor to be used when creating new instances
  4120. of the specified entity type. If this call is not made, a default constructor is created for
  4121. the entity as needed.
  4122. This call may be made before or after the corresponding EntityType has been discovered via
  4123. Metadata discovery.
  4124. @example
  4125. var Customer = function () {
  4126. this.miscData = "asdf";
  4127. };
  4128. Customer.prototype.doFoo() {
  4129. ...
  4130. }
  4131. // assume em1 is a preexisting EntityManager;
  4132. em1.metadataStore.registerEntityTypeCtor("Customer", Customer);
  4133. // any queries or EntityType.create calls from this point on will call the Customer constructor
  4134. // registered above.
  4135. @method registerEntityTypeCtor
  4136. @param structuralTypeName {String} The name of the EntityType o0r ComplexType.
  4137. @param aCtor {Function} The constructor for this EntityType or ComplexType; may be null if all you want to do is set the next parameter.
  4138. @param [initializationFn] {Function} A function or the name of a function on the entity that is to be executed immediately after the entity has been created
  4139. and populated with any initial values.
  4140. initializationFn(entity)
  4141. @param initializationFn.entity {Entity} The entity being created or materialized.
  4142. **/
  4143. proto.registerEntityTypeCtor = function (structuralTypeName, aCtor, initializationFn) {
  4144. assertParam(structuralTypeName, "structuralTypeName").isString().check();
  4145. assertParam(aCtor, "aCtor").isFunction().isOptional().check();
  4146. assertParam(initializationFn, "initializationFn").isOptional().isFunction().or().isString().check();
  4147. if (!aCtor) {
  4148. aCtor = createEmptyCtor();
  4149. }
  4150. var qualifiedTypeName = getQualifiedTypeName(this, structuralTypeName, false);
  4151. var typeName;
  4152. if (qualifiedTypeName) {
  4153. var stype = this._structuralTypeMap[qualifiedTypeName];
  4154. if (stype) {
  4155. stype._setCtor(aCtor);
  4156. }
  4157. typeName = qualifiedTypeName;
  4158. } else {
  4159. typeName = structuralTypeName;
  4160. }
  4161. aCtor.prototype._$typeName = typeName;
  4162. this._typeRegistry[typeName] = aCtor;
  4163. if (initializationFn) {
  4164. aCtor._$initializationFn = initializationFn;
  4165. }
  4166. };
  4167. function createEmptyCtor() {
  4168. return function() {};
  4169. }
  4170. /**
  4171. Returns whether this MetadataStore contains any metadata yet.
  4172. @example
  4173. // assume em1 is a preexisting EntityManager;
  4174. if (em1.metadataStore.isEmpty()) {
  4175. // do something interesting
  4176. }
  4177. @method isEmpty
  4178. @return {Boolean}
  4179. **/
  4180. proto.isEmpty = function () {
  4181. return this.dataServices.length === 0;
  4182. };
  4183. /**
  4184. Returns an {{#crossLink "EntityType"}}{{/crossLink}} or a {{#crossLink "ComplexType"}}{{/crossLink}} given its name.
  4185. @example
  4186. // assume em1 is a preexisting EntityManager
  4187. var odType = em1.metadataStore.getEntityType("OrderDetail");
  4188. or to throw an error if the type is not found
  4189. @example
  4190. var badType = em1.metadataStore.getEntityType("Foo", false);
  4191. // badType will not get set and an exception will be thrown.
  4192. @method getEntityType
  4193. @param structuralTypeName {String} Either the fully qualified name or a short name may be used. If a short name is specified and multiple types share
  4194. that same short name an exception will be thrown.
  4195. @param [okIfNotFound=false] {Boolean} Whether to throw an error if the specified EntityType is not found.
  4196. @return {EntityType|ComplexType} The EntityType. ComplexType or 'undefined' if not not found.
  4197. **/
  4198. proto.getEntityType = function (structuralTypeName, okIfNotFound) {
  4199. assertParam(structuralTypeName, "structuralTypeName").isString().check();
  4200. assertParam(okIfNotFound, "okIfNotFound").isBoolean().isOptional().check(false);
  4201. return getTypeFromMap(this, this._structuralTypeMap, structuralTypeName, okIfNotFound);
  4202. };
  4203. /**
  4204. Returns an array containing all of the {{#crossLink "EntityType"}}{{/crossLink}}s or {{#crossLink "ComplexType"}}{{/crossLink}}s in this MetadataStore.
  4205. @example
  4206. // assume em1 is a preexisting EntityManager
  4207. var allTypes = em1.metadataStore.getEntityTypes();
  4208. @method getEntityTypes
  4209. @return {Array of EntityType|ComplexType}
  4210. **/
  4211. proto.getEntityTypes = function () {
  4212. return getTypesFromMap(this._structuralTypeMap);
  4213. };
  4214. function getTypeFromMap(metadataStore, typeMap, typeName, okIfNotFound) {
  4215. var qualTypeName = getQualifiedTypeName(metadataStore, typeName, false);
  4216. var type = typeMap[qualTypeName];
  4217. if (!type) {
  4218. if (okIfNotFound) return null;
  4219. throw new Error("Unable to locate a 'Type' by the name: " + typeName);
  4220. }
  4221. if (type.length) {
  4222. var typeNames = type.join(",");
  4223. throw new Error("There are multiple types with this 'shortName': " + typeNames);
  4224. }
  4225. return type;
  4226. };
  4227. function getTypesFromMap(typeMap) {
  4228. var types = [];
  4229. for (var key in typeMap) {
  4230. var value = typeMap[key];
  4231. // skip 'shortName' entries
  4232. if (key === value.name) {
  4233. types.push(typeMap[key]);
  4234. }
  4235. }
  4236. return types;
  4237. }
  4238. proto.getIncompleteNavigationProperties = function() {
  4239. return __objectMapToArray(this._structuralTypeMap, function (key, value) {
  4240. if (value instanceof ComplexType) return null;
  4241. var badProps = value.navigationProperties.filter(function(np) {
  4242. return !np.entityType;
  4243. });
  4244. return badProps.length === 0 ? null : badProps;
  4245. });
  4246. };
  4247. /**
  4248. Returns a fully qualified entityTypeName for a specified resource name. The reverse of this operation
  4249. can be obtained via the {{#crossLink "EntityType"}}{{/crossLink}} 'defaultResourceName' property
  4250. @method getEntityTypeNameForResourceName
  4251. @param resourceName {String}
  4252. **/
  4253. proto.getEntityTypeNameForResourceName = function (resourceName) {
  4254. assertParam(resourceName, "resourceName").isString().check();
  4255. // return this._resourceEntityTypeMap[resourceName.toLowerCase()];
  4256. return this._resourceEntityTypeMap[resourceName];
  4257. };
  4258. /**
  4259. Associates a resourceName with an entityType.
  4260. This method is only needed in those cases where multiple resources return the same
  4261. entityType. In this case Metadata discovery will only determine a single resource name for
  4262. each entityType.
  4263. @method setEntityTypeForResourceName
  4264. @param resourceName {String}
  4265. @param entityTypeOrName {EntityType|String} If passing a string either the fully qualified name or a short name may be used. If a short name is specified and multiple types share
  4266. that same short name an exception will be thrown. If the entityType has not yet been discovered then a fully qualified name must be used.
  4267. **/
  4268. proto.setEntityTypeForResourceName = function (resourceName, entityTypeOrName) {
  4269. assertParam(resourceName, "resourceName").isString().check();
  4270. assertParam(entityTypeOrName, "entityTypeOrName").isInstanceOf(EntityType).or().isString().check();
  4271. // resourceName = resourceName.toLowerCase();
  4272. var entityTypeName;
  4273. if (entityTypeOrName instanceof EntityType) {
  4274. entityTypeName = entityTypeOrName.name;
  4275. } else {
  4276. entityTypeName = getQualifiedTypeName(this, entityTypeOrName, true);
  4277. }
  4278. this._resourceEntityTypeMap[resourceName] = entityTypeName;
  4279. this._entityTypeResourceMap[entityTypeName] = resourceName;
  4280. var entityType = this.getEntityType(entityTypeName, true);
  4281. if (entityType) {
  4282. entityType.defaultResourceName = entityType.defaultResourceName || resourceName;
  4283. }
  4284. };
  4285. // protected methods
  4286. proto._structuralTypeFromJson = function(json) {
  4287. var stype = this.getEntityType(json.name, true);
  4288. if (stype) return stype;
  4289. var config = {
  4290. shortName: json.shortName,
  4291. namespace: json.namespace
  4292. };
  4293. var isEntityType = !!json.navigationProperties;
  4294. stype = isEntityType ? new EntityType(config) : new ComplexType(config);
  4295. json.validators = json.validators.map(Validator.fromJSON);
  4296. json.dataProperties = json.dataProperties.map(function(dp) {
  4297. return DataProperty.fromJSON(dp, stype);
  4298. });
  4299. if (isEntityType) {
  4300. json.autoGeneratedKeyType = AutoGeneratedKeyType.fromName(json.autoGeneratedKeyType);
  4301. json.navigationProperties = json.navigationProperties.map(function(np) {
  4302. return NavigationProperty.fromJSON(np, stype);
  4303. });
  4304. }
  4305. stype = __extend(stype, json);
  4306. this.addEntityType(stype);
  4307. return stype;
  4308. };
  4309. proto._checkEntityType = function(entity) {
  4310. if (entity.entityType) return;
  4311. var typeName = entity.prototype._$typeName;
  4312. if (!typeName) {
  4313. throw new Error("This entity has not been registered. See the MetadataStore.registerEntityTypeCtor method");
  4314. }
  4315. var entityType = this.getEntityType(typeName);
  4316. if (entityType) {
  4317. entity.entityType = entityType;
  4318. }
  4319. };
  4320. proto._parseODataMetadata = function (serviceName, schemas) {
  4321. var that = this;
  4322. toArray(schemas).forEach(function (schema) {
  4323. if (schema.cSpaceOSpaceMapping) {
  4324. // Web api only - not avail in OData.
  4325. var mappings = JSON.parse(schema.cSpaceOSpaceMapping);
  4326. var newMap = {};
  4327. mappings.forEach(function(mapping) {
  4328. newMap[mapping[0]] = mapping[1];
  4329. });
  4330. schema.cSpaceOSpaceMapping = newMap;
  4331. }
  4332. if (schema.entityContainer) {
  4333. toArray(schema.entityContainer).forEach(function (container) {
  4334. toArray(container.entitySet).forEach(function (entitySet) {
  4335. var entityTypeName = normalizeTypeName(entitySet.entityType, schema).typeName;
  4336. that.setEntityTypeForResourceName(entitySet.name, entityTypeName);
  4337. });
  4338. });
  4339. }
  4340. // process complextypes before entity types.
  4341. if (schema.complexType) {
  4342. toArray(schema.complexType).forEach(function (ct) {
  4343. var complexType = convertFromODataComplexType(ct, schema, that);
  4344. checkTypeRegistry(that, complexType);
  4345. });
  4346. }
  4347. if (schema.entityType) {
  4348. toArray(schema.entityType).forEach(function (et) {
  4349. var entityType = convertFromODataEntityType(et, schema, that);
  4350. checkTypeRegistry(that, entityType);
  4351. });
  4352. }
  4353. });
  4354. var badNavProps = this.getIncompleteNavigationProperties();
  4355. if (badNavProps.length > 0) {
  4356. throw new Error("Bad nav properties");
  4357. }
  4358. };
  4359. function checkTypeRegistry(metadataStore, structuralType) {
  4360. // check if this structural type's name, short version or qualified version has a registered ctor.
  4361. var typeCtor = metadataStore._typeRegistry[structuralType.name] || metadataStore._typeRegistry[structuralType.shortName];
  4362. if (typeCtor) {
  4363. // next line is in case the entityType was originally registered with a shortname.
  4364. typeCtor.prototype._$typeName = structuralType.name;
  4365. structuralType._setCtor(typeCtor);
  4366. metadataStore._structuralTypeMap[structuralType.name] = structuralType;
  4367. }
  4368. }
  4369. function getQualifiedTypeName(metadataStore, structTypeName, throwIfNotFound) {
  4370. if (isQualifiedTypeName(structTypeName)) return structTypeName;
  4371. var result = metadataStore._shortNameMap[structTypeName];
  4372. if (!result && throwIfNotFound) {
  4373. throw new Error("Unable to locate 'entityTypeName' of: " + structTypeName);
  4374. }
  4375. return result;
  4376. }
  4377. function convertFromODataEntityType(odataEntityType, schema, metadataStore) {
  4378. var shortName = odataEntityType.name;
  4379. var ns = getNamespaceFor(shortName, schema);
  4380. var entityType = new EntityType({
  4381. shortName: shortName,
  4382. namespace: ns
  4383. });
  4384. var keyNamesOnServer = toArray(odataEntityType.key.propertyRef).map(__pluck("name"));
  4385. toArray(odataEntityType.property).forEach(function (prop) {
  4386. convertFromODataDataProperty(entityType, prop, schema, keyNamesOnServer);
  4387. });
  4388. toArray(odataEntityType.navigationProperty).forEach(function (prop) {
  4389. convertFromODataNavProperty(entityType, prop, schema);
  4390. });
  4391. metadataStore.addEntityType(entityType);
  4392. return entityType;
  4393. }
  4394. function convertFromODataComplexType(odataComplexType, schema, metadataStore) {
  4395. var shortName = odataComplexType.name;
  4396. var ns = getNamespaceFor(shortName, schema);
  4397. var complexType = new ComplexType({
  4398. shortName: shortName,
  4399. namespace: ns
  4400. });
  4401. toArray(odataComplexType.property).forEach(function (prop) {
  4402. convertFromODataDataProperty(complexType, prop, schema);
  4403. });
  4404. metadataStore.addEntityType(complexType);
  4405. return complexType;
  4406. }
  4407. function convertFromODataDataProperty(parentType, odataProperty, schema, keyNamesOnServer) {
  4408. var dp;
  4409. var typeParts = odataProperty.type.split(".");
  4410. if (typeParts.length == 2) {
  4411. dp = convertFromODataSimpleProperty(parentType, odataProperty, keyNamesOnServer);
  4412. } else {
  4413. if (isEnumType(odataProperty, schema)) {
  4414. dp = convertFromODataSimpleProperty(parentType, odataProperty, keyNamesOnServer);
  4415. if (dp) {
  4416. dp.enumType = odataProperty.type;
  4417. }
  4418. } else {
  4419. dp = convertFromODataComplexProperty(parentType, odataProperty, schema);
  4420. }
  4421. }
  4422. if (dp) {
  4423. parentType.addProperty(dp);
  4424. addValidators(dp);
  4425. }
  4426. return dp;
  4427. }
  4428. function isEnumType(odataProperty, schema) {
  4429. if (!schema.enumType) return false;
  4430. var enumTypes = toArray(schema.enumType);
  4431. var typeParts = odataProperty.type.split(".");
  4432. var baseTypeName = typeParts[typeParts.length - 1];
  4433. return enumTypes.some(function(enumType) {
  4434. return enumType.name === baseTypeName;
  4435. });
  4436. }
  4437. function convertFromODataSimpleProperty(parentType, odataProperty, keyNamesOnServer) {
  4438. var dataType = DataType.fromEdmDataType(odataProperty.type);
  4439. if (dataType == null) {
  4440. parentType.warnings.push("Unable to recognize DataType for property: " + odataProperty.name + " DateType: " + odataProperty.type);
  4441. return null;
  4442. }
  4443. var isNullable = odataProperty.nullable === 'true' || odataProperty.nullable == null;
  4444. var fixedLength = odataProperty.fixedLength ? odataProperty.fixedLength === true : undefined;
  4445. var isPartOfKey = keyNamesOnServer!=null && keyNamesOnServer.indexOf(odataProperty.name) >= 0;
  4446. if (parentType.autoGeneratedKeyType == AutoGeneratedKeyType.None) {
  4447. if (isIdentityProperty(odataProperty)) {
  4448. parentType.autoGeneratedKeyType = AutoGeneratedKeyType.Identity;
  4449. }
  4450. }
  4451. var maxLength = odataProperty.maxLength;
  4452. maxLength = (maxLength == null || maxLength==="Max") ? null : parseInt(maxLength);
  4453. // can't set the name until we go thru namingConventions and these need the dp.
  4454. var dp = new DataProperty({
  4455. nameOnServer: odataProperty.name,
  4456. dataType: dataType,
  4457. isNullable: isNullable,
  4458. isPartOfKey: isPartOfKey,
  4459. maxLength: maxLength,
  4460. fixedLength: fixedLength,
  4461. concurrencyMode: odataProperty.concurrencyMode
  4462. });
  4463. if (dataType === DataType.Undefined) {
  4464. dp.rawTypeName = odataProperty.type;
  4465. }
  4466. return dp;
  4467. }
  4468. function convertFromODataComplexProperty(parentType, odataProperty, schema) {
  4469. // Complex properties are never nullable ( per EF specs)
  4470. // var isNullable = odataProperty.nullable === 'true' || odataProperty.nullable == null;
  4471. // var complexTypeName = odataProperty.type.split("Edm.")[1];
  4472. var complexTypeName = normalizeTypeName(odataProperty.type, schema).typeName;
  4473. // can't set the name until we go thru namingConventions and these need the dp.
  4474. var dp = new DataProperty({
  4475. nameOnServer: odataProperty.name,
  4476. complexTypeName: complexTypeName,
  4477. isNullable: false
  4478. });
  4479. return dp;
  4480. }
  4481. function addValidators(dataProperty) {
  4482. var typeValidator;
  4483. if (!dataProperty.isNullable) {
  4484. dataProperty.validators.push(Validator.required());
  4485. }
  4486. if (dataProperty.isComplexProperty) return;
  4487. if (dataProperty.dataType === DataType.String) {
  4488. if (dataProperty.maxLength) {
  4489. var validatorArgs = { maxLength: dataProperty.maxLength };
  4490. typeValidator = Validator.maxLength(validatorArgs);
  4491. } else {
  4492. typeValidator = Validator.string();
  4493. }
  4494. } else {
  4495. typeValidator = dataProperty.dataType.validatorCtor();
  4496. }
  4497. dataProperty.validators.push(typeValidator);
  4498. }
  4499. function convertFromODataNavProperty(entityType, odataProperty, schema) {
  4500. var association = getAssociation(odataProperty, schema);
  4501. var toEnd = __arrayFirst(association.end, function (assocEnd) {
  4502. return assocEnd.role === odataProperty.toRole;
  4503. });
  4504. var isScalar = !(toEnd.multiplicity === "*");
  4505. var dataType = normalizeTypeName(toEnd.type, schema).typeName;
  4506. var fkNamesOnServer = [];
  4507. if (toEnd && isScalar) {
  4508. var constraint = association.referentialConstraint;
  4509. if (constraint) {
  4510. var principal = constraint.principal;
  4511. var dependent = constraint.dependent;
  4512. var propRefs;
  4513. if (odataProperty.fromRole === principal.role) {
  4514. propRefs = toArray(principal.propertyRef);
  4515. } else {
  4516. propRefs = toArray(dependent.propertyRef);
  4517. }
  4518. // will be used later by np._update
  4519. fkNamesOnServer = propRefs.map(__pluck("name"));
  4520. }
  4521. }
  4522. var np = new NavigationProperty({
  4523. nameOnServer: odataProperty.name,
  4524. entityTypeName: dataType,
  4525. isScalar: isScalar,
  4526. associationName: association.name,
  4527. foreignKeyNamesOnServer: fkNamesOnServer
  4528. });
  4529. entityType.addProperty(np);
  4530. return np;
  4531. }
  4532. function isIdentityProperty(odataProperty) {
  4533. // see if web api feed
  4534. var propName = __arrayFirst(Object.keys(odataProperty), function (pn) {
  4535. return pn.indexOf("StoreGeneratedPattern") >= 0;
  4536. });
  4537. if (propName) {
  4538. return (odataProperty[propName] === "Identity");
  4539. } else {
  4540. // see if Odata feed
  4541. var extensions = odataProperty.extensions;
  4542. if (!extensions) {
  4543. return false;
  4544. }
  4545. var identityExtn = __arrayFirst(extensions, function (extension) {
  4546. return extension.name === "StoreGeneratedPattern" && extension.value === "Identity";
  4547. });
  4548. return !!identityExtn;
  4549. }
  4550. }
  4551. // Fast version
  4552. // np: schema.entityType[].navigationProperty.relationship -> schema.association
  4553. // match( shortName(np.relationship) == schema.association[].name
  4554. // --> association
  4555. // Correct version
  4556. // np: schema.entityType[].navigationProperty.relationship -> schema.association
  4557. // match( np.relationship == schema.entityContainer[0].associationSet[].association )
  4558. // -> associationSet.name
  4559. // match ( associationSet.name == schema.association[].name )
  4560. // -> association
  4561. function getAssociation(odataNavProperty, schema) {
  4562. var assocName = normalizeTypeName(odataNavProperty.relationship, schema).shortTypeName;
  4563. var assocs = schema.association;
  4564. if (!assocs) return null;
  4565. if (!Array.isArray(assocs)) {
  4566. assocs = [assocs];
  4567. }
  4568. var association = __arrayFirst(assocs, function (assoc) {
  4569. return assoc.name === assocName;
  4570. });
  4571. return association;
  4572. }
  4573. function toArray(item) {
  4574. if (!item) {
  4575. return [];
  4576. } else if (Array.isArray(item)) {
  4577. return item;
  4578. } else {
  4579. return [item];
  4580. }
  4581. }
  4582. return ctor;
  4583. })();
  4584. var DataService = function () {
  4585. /**
  4586. A DataService instance is used to encapsulate the details of a single 'service'; this includes a serviceName, a dataService adapterInstance,
  4587. and whether the service has server side metadata.
  4588. You can construct an EntityManager with either a serviceName or a DataService instance, if you use a serviceName then a DataService
  4589. is constructed for you. (It can also be set via the EntityManager.setProperties method).
  4590. The same applies to the MetadataStore.fetchMetadata method, i.e. it takes either a serviceName or a DataService instance.
  4591. Each metadataStore contains a list of DataServices, each accessible via its ‘serviceName’.
  4592. ( see MetadataStore.getDataService and MetadataStore.addDataService). The ‘addDataService’ method is called internally
  4593. anytime a MetadataStore.fetchMetadata call occurs with a new dataService ( or service name).
  4594. @class DataService
  4595. **/
  4596. /**
  4597. DataService constructor
  4598. @example
  4599. //
  4600. var dataService = new DataService({
  4601. serviceName: altServiceName,
  4602. hasServerMetadata: false
  4603. });
  4604. var metadataStore = new MetadataStore({
  4605. namingConvention: NamingConvention.camelCase
  4606. });
  4607. return new EntityManager({
  4608. dataService: dataService,
  4609. metadataStore: metadataStore
  4610. });
  4611. @method <ctor> DataService
  4612. @param config {Object}
  4613. @param config.serviceName {String} The name of the service.
  4614. @param [config.adapterName] {String} The name of the dataServiceAdapter to be used with this service.
  4615. @param [config.hasServerMetadata] {bool} Whether the server can provide metadata for this service.
  4616. @param [config.jsonResultsAdapter] {JsonResultsAdapter} The JsonResultsAdapter used to process the results of any query against this service.
  4617. **/
  4618. var ctor = function(config) {
  4619. if (arguments.length != 1) {
  4620. throw new Error("The DataService ctor should be called with a single argument that is a configuration object.");
  4621. }
  4622. assertConfig(config)
  4623. .whereParam("serviceName").isNonEmptyString()
  4624. .whereParam("adapterName").isString().isOptional().withDefault(null)
  4625. .whereParam("hasServerMetadata").isBoolean().isOptional().withDefault(true)
  4626. .whereParam("jsonResultsAdapter").isInstanceOf(JsonResultsAdapter).isOptional().withDefault(null)
  4627. .applyAll(this);
  4628. this.serviceName = DataService._normalizeServiceName(this.serviceName);
  4629. this.adapterInstance = __config.getAdapterInstance("dataService", this.adapterName);
  4630. if (!this.jsonResultsAdapter) {
  4631. this.jsonResultsAdapter = this.adapterInstance.jsonResultsAdapter;
  4632. }
  4633. };
  4634. var proto = ctor.prototype;
  4635. proto._$typeName = "DataService";
  4636. /**
  4637. The serviceName for this DataService.
  4638. __readOnly__
  4639. @property serviceName {String}
  4640. **/
  4641. /**
  4642. The adapter name for the dataServiceAdapter to be used with this service.
  4643. __readOnly__
  4644. @property adapterName {String}
  4645. **/
  4646. /**
  4647. The "dataService" adapter implementation instance associated with this EntityManager.
  4648. __readOnly__
  4649. @property adapterInstance {an instance of the "dataService" adapter interface}
  4650. **/
  4651. /**
  4652. Whether the server can provide metadata for this service.
  4653. __readOnly__
  4654. @property hasServerMetadata {Boolean}
  4655. **/
  4656. /**
  4657. The JsonResultsAdapter used to process the results of any query against this DataService.
  4658. __readOnly__
  4659. @property jsonResultsAdapter {Boolean}
  4660. **/
  4661. ctor._normalizeServiceName = function(serviceName) {
  4662. serviceName = serviceName.trim();
  4663. if (serviceName.substr(-1) !== "/") {
  4664. return serviceName + '/';
  4665. } else {
  4666. return serviceName;
  4667. }
  4668. };
  4669. proto.toJSON = function () {
  4670. var json = __toJson(this);
  4671. json.jsonResultsAdapter = this.jsonResultsAdapter.name;
  4672. return json;
  4673. };
  4674. ctor.fromJSON = function(json) {
  4675. json.jsonResultsAdapter = __config._fetchObject(JsonResultsAdapter, json.jsonResultsAdapter);
  4676. return new DataService(json);
  4677. };
  4678. return ctor;
  4679. }();
  4680. var JsonResultsAdapter = (function () {
  4681. /**
  4682. A JsonResultsAdapter instance is used to provide custom extraction and parsing logic on the json results returned by any web service.
  4683. This facility makes it possible for breeze to talk to virtually any web service and return objects that will be first class 'breeze' citizens.
  4684. @class JsonResultsAdapter
  4685. **/
  4686. /**
  4687. JsonResultsAdapter constructor
  4688. @example
  4689. //
  4690. var jsonResultsAdapter = new JsonResultsAdapter({
  4691. name: "test1e",
  4692. extractResults: function(json) {
  4693. return json.results;
  4694. },
  4695. visitNode: function(node, queryContext, nodeContext) {
  4696. var entityTypeName = normalizeTypeName(node.$type);
  4697. var entityType = entityTypeName && queryContext.entityManager.metadataStore.getEntityType(entityTypeName, true);
  4698. var propertyName = nodeContext.propertyName;
  4699. var ignore = propertyName && propertyName.substr(0, 1) === "$";
  4700. return {
  4701. entityType: entityType,
  4702. nodeId: node.$id,
  4703. nodeRefId: node.$ref,
  4704. ignore: ignore
  4705. };
  4706. }
  4707. });
  4708. var dataService = new DataService( {
  4709. serviceName: "api/foo",
  4710. jsonResultsAdapter: jsonResultsAdapter
  4711. });
  4712. var entityManager = new EntityManager( {
  4713. dataService: dataService
  4714. });
  4715. @method <ctor> JsonResultsAdapter
  4716. @param config {Object}
  4717. @param config.name {String} The name of this adapter. This name is used to uniquely identify and locate this instance when an 'exported' JsonResultsAdapter is later imported.
  4718. @param [config.extractResults] {Function} Called once per service operation to extract the 'payload' from any json received over the wire.
  4719. This method has a default implementation which to simply return the "results" property from any json returned as a result of executing the query.
  4720. @param config.visitNode {Function} A visitor method that will be called on each node of the returned payload.
  4721. **/
  4722. var ctor = function (config) {
  4723. if (arguments.length != 1) {
  4724. throw new Error("The JsonResultsAdapter ctor should be called with a single argument that is a configuration object.");
  4725. }
  4726. assertConfig(config)
  4727. .whereParam("name").isNonEmptyString()
  4728. .whereParam("extractResults").isFunction().isOptional().withDefault(extractResultsDefault)
  4729. .whereParam("visitNode").isFunction()
  4730. .applyAll(this);
  4731. __config._storeObject(this, proto._$typeName, this.name);
  4732. };
  4733. var proto = ctor.prototype;
  4734. proto._$typeName = "JsonResultsAdapter";
  4735. function extractResultsDefault(data) {
  4736. return data.results;
  4737. }
  4738. return ctor;
  4739. })();
  4740. var EntityType = (function () {
  4741. /**
  4742. Container for all of the metadata about a specific type of Entity.
  4743. @class EntityType
  4744. **/
  4745. var __nextAnonIx = 0;
  4746. /**
  4747. @example
  4748. var entityType = new EntityType( {
  4749. shortName: "person",
  4750. namespace: "myAppNamespace"
  4751. });
  4752. @method <ctor> EntityType
  4753. @param config {Object|MetadataStore} Configuration settings or a MetadataStore. If this parameter is just a MetadataStore
  4754. then what will be created is an 'anonymous' type that will never be communicated to or from the server. It is purely for
  4755. client side use and will be given an automatically generated name. Normally, however, you will use a configuration object.
  4756. @param config.shortName {String}
  4757. @param [config.namespace=""] {String}
  4758. @param [config.autogeneratedKeyType] {AutoGeneratedKeyType}
  4759. @param [config.defaultResourceName] { String}
  4760. **/
  4761. var ctor = function (config) {
  4762. if (arguments.length > 1) {
  4763. throw new Error("The EntityType ctor has a single argument that is either a 'MetadataStore' or a configuration object.");
  4764. }
  4765. if (config._$typeName === "MetadataStore") {
  4766. this.metadataStore = config;
  4767. this.shortName = "Anon_" + ++__nextAnonIx;
  4768. this.namespace = "";
  4769. this.isAnonymous = true;
  4770. } else {
  4771. assertConfig(config)
  4772. .whereParam("shortName").isNonEmptyString()
  4773. .whereParam("namespace").isString().isOptional().withDefault("")
  4774. .whereParam("autoGeneratedKeyType").isEnumOf(AutoGeneratedKeyType).isOptional().withDefault(AutoGeneratedKeyType.None)
  4775. .whereParam("defaultResourceName").isNonEmptyString().isOptional().withDefault(null)
  4776. .applyAll(this);
  4777. }
  4778. this.name = qualifyTypeName(this.shortName, this.namespace);
  4779. // the defaultResourceName may also be set up either via metadata lookup or first query or via the 'setProperties' method
  4780. this.dataProperties = [];
  4781. this.navigationProperties = [];
  4782. this.complexProperties = [];
  4783. this.keyProperties = [];
  4784. this.foreignKeyProperties = [];
  4785. this.concurrencyProperties = [];
  4786. this.unmappedProperties = []; // will be updated later.
  4787. this.validators = [];
  4788. this.warnings = [];
  4789. this._mappedPropertiesCount = 0;
  4790. };
  4791. var proto = ctor.prototype;
  4792. proto._$typeName = "EntityType";
  4793. /**
  4794. The {{#crossLink "MetadataStore"}}{{/crossLink}} that contains this EntityType
  4795. __readOnly__
  4796. @property metadataStore {MetadataStore}
  4797. **/
  4798. /**
  4799. The DataProperties (see {{#crossLink "DataProperty"}}{{/crossLink}}) associated with this EntityType.
  4800. __readOnly__
  4801. @property dataProperties {Array of DataProperty}
  4802. **/
  4803. /**
  4804. The NavigationProperties (see {{#crossLink "NavigationProperty"}}{{/crossLink}}) associated with this EntityType.
  4805. __readOnly__
  4806. @property navigationProperties {Array of NavigationProperty}
  4807. **/
  4808. /**
  4809. The DataProperties for this EntityType that contain instances of a ComplexType (see {{#crossLink "ComplexType"}}{{/crossLink}}).
  4810. __readOnly__
  4811. @property complexProperties {Array of DataProperty}
  4812. **/
  4813. /**
  4814. The DataProperties associated with this EntityType that make up it's {{#crossLink "EntityKey"}}{{/crossLink}}.
  4815. __readOnly__
  4816. @property keyProperties {Array of DataProperty}
  4817. **/
  4818. /**
  4819. The DataProperties associated with this EntityType that are foreign key properties.
  4820. __readOnly__
  4821. @property foreignKeyProperties {Array of DataProperty}
  4822. **/
  4823. /**
  4824. The DataProperties associated with this EntityType that are concurrency properties.
  4825. __readOnly__
  4826. @property concurrencyProperties {Array of DataProperty}
  4827. **/
  4828. /**
  4829. The DataProperties associated with this EntityType that are not mapped to any backend datastore. These are effectively free standing
  4830. properties.
  4831. __readOnly__
  4832. @property unmappedProperties {Array of DataProperty}
  4833. **/
  4834. /**
  4835. The default resource name associated with this EntityType. An EntityType may be queried via a variety of 'resource names' but this one
  4836. is used as the default when no resource name is provided. This will occur when calling {{#crossLink "EntityAspect/loadNavigationProperty"}}{{/crossLink}}
  4837. or when executing any {{#crossLink "EntityQuery"}}{{/crossLink}} that was created via an {{#crossLink "EntityKey"}}{{/crossLink}}.
  4838. __readOnly__
  4839. @property defaultResourceName {String}
  4840. **/
  4841. /**
  4842. The fully qualifed name of this EntityType.
  4843. __readOnly__
  4844. @property name {String}
  4845. **/
  4846. /**
  4847. The short, unqualified, name for this EntityType.
  4848. __readOnly__
  4849. @property shortName {String}
  4850. **/
  4851. /**
  4852. The namespace for this EntityType.
  4853. __readOnly__
  4854. @property namespace {String}
  4855. **/
  4856. /**
  4857. The {{#crossLink "AutoGeneratedKeyType"}}{{/crossLink}} for this EntityType.
  4858. __readOnly__
  4859. @property autoGeneratedKeyType {AutoGeneratedKeyType}
  4860. @default AutoGeneratedKeyType.None
  4861. **/
  4862. /**
  4863. The entity level validators associated with this EntityType. Validators can be added and
  4864. removed from this collection.
  4865. __readOnly__
  4866. @property validators {Array of Validator}
  4867. **/
  4868. /**
  4869. General purpose property set method
  4870. @example
  4871. // assume em1 is an EntityManager containing a number of existing entities.
  4872. var custType = em1.metadataStore.getEntityType("Customer");
  4873. custType.setProperties( {
  4874. autoGeneratedKeyType: AutoGeneratedKeyType.Identity;
  4875. defaultResourceName: "CustomersAndIncludedOrders"
  4876. )};
  4877. @method setProperties
  4878. @param config [object]
  4879. @param [config.autogeneratedKeyType] {AutoGeneratedKeyType}
  4880. @param [config.defaultResourceName] {String}
  4881. **/
  4882. proto.setProperties = function (config) {
  4883. assertConfig(config)
  4884. .whereParam("autoGeneratedKeyType").isEnumOf(AutoGeneratedKeyType).isOptional()
  4885. .whereParam("defaultResourceName").isString().isOptional()
  4886. .applyAll(this);
  4887. if (config.defaultResourceName) {
  4888. this.defaultResourceName = config.defaultResourceName;
  4889. }
  4890. };
  4891. /**
  4892. Adds a {{#crossLink "DataProperty"}}{{/crossLink}} or a {{#crossLink "NavigationProperty"}}{{/crossLink}} to this EntityType.
  4893. @example
  4894. // assume myEntityType is a newly constructed EntityType.
  4895. myEntityType.addProperty(dataProperty1);
  4896. myEntityType.addProperty(dataProperty2);
  4897. myEntityType.addProperty(navigationProperty1);
  4898. @method addProperty
  4899. @param property {DataProperty|NavigationProperty}
  4900. **/
  4901. proto.addProperty = function (property) {
  4902. assertParam(property, "dataProperty").isInstanceOf(DataProperty).or().isInstanceOf(NavigationProperty).check();
  4903. if (this.metadataStore && !property.isUnmapped) {
  4904. throw new Error("The '" + this.name + "' EntityType has already been added to a MetadataStore and therefore no additional properties may be added to it.");
  4905. }
  4906. if (property.parentType) {
  4907. if (property.parentType !== this) {
  4908. throw new Error("This dataProperty has already been added to " + property.parentType.name);
  4909. } else {
  4910. return this;
  4911. }
  4912. }
  4913. property.parentType = this;
  4914. if (property.isDataProperty) {
  4915. this._addDataProperty(property);
  4916. } else {
  4917. this._addNavigationProperty(property);
  4918. }
  4919. return this;
  4920. };
  4921. /**
  4922. Create a new entity of this type.
  4923. @example
  4924. // assume em1 is an EntityManager containing a number of existing entities.
  4925. var custType = em1.metadataStore.getEntityType("Customer");
  4926. var cust1 = custType.createEntity();
  4927. em1.addEntity(cust1);
  4928. @method createEntity
  4929. @param [initialValues] {Config object} - Configuration object of the properties to set immediately after creation.
  4930. @return {Entity} The new entity.
  4931. **/
  4932. proto.createEntity = function (initialValues) {
  4933. var instance = this._createEntityCore();
  4934. if (initialValues) {
  4935. __objectForEach(initialValues, function(key, value) {
  4936. instance.setProperty(key, value);
  4937. });
  4938. }
  4939. instance.entityAspect._postInitialize();
  4940. return instance;
  4941. };
  4942. proto._createEntityCore = function() {
  4943. var aCtor = this.getEntityCtor();
  4944. var instance = new aCtor();
  4945. new EntityAspect(instance);
  4946. return instance;
  4947. };
  4948. /**
  4949. Returns the constructor for this EntityType.
  4950. @method getEntityCtor
  4951. @return {Function} The constructor for this EntityType.
  4952. **/
  4953. proto.getEntityCtor = function () {
  4954. if (this._ctor) return this._ctor;
  4955. var typeRegistry = this.metadataStore._typeRegistry;
  4956. var aCtor = typeRegistry[this.name] || typeRegistry[this.shortName];
  4957. if (!aCtor) {
  4958. var createCtor = __modelLibraryDef.getDefaultInstance().createCtor;
  4959. if (createCtor) {
  4960. aCtor = createCtor(this);
  4961. } else {
  4962. aCtor = createEmptyCtor();
  4963. }
  4964. }
  4965. this._setCtor(aCtor);
  4966. return aCtor;
  4967. };
  4968. function createEmptyCtor() {
  4969. return function() { };
  4970. }
  4971. // May make public later.
  4972. proto._setCtor = function (aCtor, interceptor) {
  4973. var instance = new aCtor();
  4974. var proto = aCtor.prototype;
  4975. if (this._$typeName == "EntityType") {
  4976. // insure that all of the properties are on the 'template' instance before watching the class.
  4977. calcUnmappedProperties(this, instance);
  4978. proto.entityType = this;
  4979. } else {
  4980. calcUnmappedProperties(this, instance);
  4981. proto.complexType = this;
  4982. }
  4983. if (interceptor) {
  4984. proto._$interceptor = interceptor;
  4985. } else {
  4986. proto._$interceptor = defaultPropertyInterceptor;
  4987. }
  4988. __modelLibraryDef.getDefaultInstance().initializeEntityPrototype(proto);
  4989. this._ctor = aCtor;
  4990. };
  4991. /**
  4992. Adds either an entity or property level validator to this EntityType.
  4993. @example
  4994. // assume em1 is an EntityManager containing a number of existing entities.
  4995. var custType = em1.metadataStore.getEntityType("Customer");
  4996. var countryProp = custType.getProperty("Country");
  4997. var valFn = function (v) {
  4998. if (v == null) return true;
  4999. return (core.stringStartsWith(v, "US"));
  5000. };
  5001. var countryValidator = new Validator("countryIsUS", valFn,
  5002. { displayName: "Country", messageTemplate: "'%displayName%' must start with 'US'" });
  5003. custType.addValidator(countryValidator, countryProp);
  5004. This is the same as adding an entity level validator via the 'validators' property of DataProperty or NavigationProperty
  5005. @example
  5006. countryProp.validators.push(countryValidator);
  5007. Entity level validators can also be added by omitting the 'property' parameter.
  5008. @example
  5009. custType.addValidator(someEntityLevelValidator);
  5010. or
  5011. @example
  5012. custType.validators.push(someEntityLevelValidator);
  5013. @method addValidator
  5014. @param validator {Validator} Validator to add.
  5015. @param [property] Property to add this validator to. If omitted, the validator is assumed to be an
  5016. entity level validator and is added to the EntityType's 'validators'.
  5017. **/
  5018. proto.addValidator = function (validator, property) {
  5019. assertParam(validator, "validator").isInstanceOf(Validator).check();
  5020. assertParam(property, "property").isOptional().isString().or().isEntityProperty().check();
  5021. if (property) {
  5022. if (typeof (property) === 'string') {
  5023. property = this.getProperty(property, true);
  5024. }
  5025. property.validators.push(validator);
  5026. } else {
  5027. this.validators.push(validator);
  5028. }
  5029. };
  5030. /**
  5031. Returns all of the properties ( dataProperties and navigationProperties) for this EntityType.
  5032. @example
  5033. // assume em1 is an EntityManager containing a number of existing entities.
  5034. var custType = em1.metadataStore.getEntityType("Customer");
  5035. var arrayOfProps = custType.getProperties();
  5036. @method getProperties
  5037. @return {Array of DataProperty|NavigationProperty} Array of Data and Navigation properties.
  5038. **/
  5039. proto.getProperties = function () {
  5040. return this.dataProperties.concat(this.navigationProperties);
  5041. };
  5042. /**
  5043. Returns all of the property names ( for both dataProperties and navigationProperties) for this EntityType.
  5044. @example
  5045. // assume em1 is an EntityManager containing a number of existing entities.
  5046. var custType = em1.metadataStore.getEntityType("Customer");
  5047. var arrayOfPropNames = custType.getPropertyNames();
  5048. @method getPropertyNames
  5049. @return {Array of String}
  5050. **/
  5051. proto.getPropertyNames = function () {
  5052. return this.getProperties().map(__pluck('name'));
  5053. };
  5054. /**
  5055. Returns a data property with the specified name or null.
  5056. @example
  5057. // assume em1 is an EntityManager containing a number of existing entities.
  5058. var custType = em1.metadataStore.getEntityType("Customer");
  5059. var customerNameDataProp = custType.getDataProperty("CustomerName");
  5060. @method getDataProperty
  5061. @param propertyName {String}
  5062. @return {DataProperty} Will be null if not found.
  5063. **/
  5064. proto.getDataProperty = function (propertyName, isServerName) {
  5065. var propName = isServerName ? "nameOnServer" : "name";
  5066. return __arrayFirst(this.dataProperties, __propEq(propName, propertyName));
  5067. };
  5068. /**
  5069. Returns a navigation property with the specified name or null.
  5070. @example
  5071. // assume em1 is an EntityManager containing a number of existing entities.
  5072. var custType = em1.metadataStore.getEntityType("Customer");
  5073. var customerOrdersNavProp = custType.getDataProperty("Orders");
  5074. @method getNavigationProperty
  5075. @param propertyName {String}
  5076. @return {NavigationProperty} Will be null if not found.
  5077. **/
  5078. proto.getNavigationProperty = function (propertyName, isServerName) {
  5079. var propName = isServerName ? "nameOnServer" : "name";
  5080. return __arrayFirst(this.navigationProperties, __propEq(propName, propertyName));
  5081. };
  5082. /**
  5083. Returns either a DataProperty or a NavigationProperty with the specified name or null.
  5084. This method also accepts a '.' delimited property path and will return the 'property' at the
  5085. end of the path.
  5086. @example
  5087. var custType = em1.metadataStore.getEntityType("Customer");
  5088. var companyNameProp = custType.getProperty("CompanyName");
  5089. This method can also walk a property path to return a property
  5090. @example
  5091. var orderDetailType = em1.metadataStore.getEntityType("OrderDetail");
  5092. var companyNameProp2 = orderDetailType.getProperty("Order.Customer.CompanyName");
  5093. // companyNameProp === companyNameProp2
  5094. @method getProperty
  5095. @param propertyPath {String}
  5096. @param [throwIfNotFound=false] {Boolean} Whether to throw an exception if not found.
  5097. @return {DataProperty|NavigationProperty} Will be null if not found.
  5098. **/
  5099. proto.getProperty = function (propertyPath, throwIfNotFound) {
  5100. throwIfNotFound = throwIfNotFound || false;
  5101. var propertyNames = (Array.isArray(propertyPath)) ? propertyPath : propertyPath.trim().split('.');
  5102. var propertyName = propertyNames[0];
  5103. var prop = __arrayFirst(this.getProperties(), __propEq("name", propertyName));
  5104. if (propertyNames.length === 1) {
  5105. if (prop) {
  5106. return prop;
  5107. } else if (throwIfNotFound) {
  5108. throw new Error("unable to locate property: " + propertyName + " on entityType: " + this.name);
  5109. } else {
  5110. return null;
  5111. }
  5112. } else {
  5113. if (prop) {
  5114. propertyNames.shift();
  5115. // dataType is line below will be a complexType
  5116. var nextParentType = prop.isNavigationProperty ? prop.entityType : prop.dataType;
  5117. if (nextParentType) {
  5118. return nextParentType.getProperty(propertyNames, throwIfNotFound);
  5119. } else {
  5120. throw new Error("should not get here - unknown property type for: " + prop.name);
  5121. }
  5122. } else {
  5123. if (throwIfNotFound) {
  5124. throw new Error("unable to locate property: " + propertyName + " on type: " + this.name);
  5125. } else {
  5126. return null;
  5127. }
  5128. }
  5129. }
  5130. };
  5131. /**
  5132. Returns a string representation of this EntityType.
  5133. @method toString
  5134. @return {String}
  5135. **/
  5136. proto.toString = function () {
  5137. return this.name;
  5138. };
  5139. proto.toJSON = function () {
  5140. return __toJson(this, ["dataProperties", "navigationProperties", "validators"]);
  5141. };
  5142. // fromJSON is handled by metadataStore._structuralTypeFromJson;
  5143. proto._clientPropertyPathToServer = function (propertyPath) {
  5144. var fn = this.metadataStore.namingConvention.clientPropertyNameToServer;
  5145. var that = this;
  5146. var serverPropPath = propertyPath.split(".").map(function (propName) {
  5147. var prop = that.getProperty(propName);
  5148. return fn(propName, prop);
  5149. }).join("/");
  5150. return serverPropPath;
  5151. };
  5152. proto._updateProperty = function (property) {
  5153. var nc = this.metadataStore.namingConvention;
  5154. var serverName = property.nameOnServer;
  5155. var clientName, testName;
  5156. if (serverName) {
  5157. clientName = nc.serverPropertyNameToClient(serverName, property);
  5158. testName = nc.clientPropertyNameToServer(clientName, property);
  5159. if (serverName !== testName) {
  5160. throw new Error("NamingConvention for this server property name does not roundtrip properly:" + serverName + "-->" + testName);
  5161. }
  5162. property.name = clientName;
  5163. } else {
  5164. clientName = property.name;
  5165. serverName = nc.clientPropertyNameToServer(clientName, property);
  5166. testName = nc.serverPropertyNameToClient(serverName, property);
  5167. if (clientName !== testName) {
  5168. throw new Error("NamingConvention for this client property name does not roundtrip properly:" + clientName + "-->" + testName);
  5169. }
  5170. property.nameOnServer = serverName;
  5171. }
  5172. if (property.isComplexProperty) {
  5173. // Not ok to not find it. - all complex types should be resolved before they are ref'd.
  5174. var targetComplexType = this.metadataStore.getEntityType(property.complexTypeName, false);
  5175. if (targetComplexType && targetComplexType instanceof ComplexType) {
  5176. property.dataType = targetComplexType;
  5177. property.defaultValue = null;
  5178. } else {
  5179. throw new Error("Unable to resolve ComplexType with the name: " + property.complexTypeName + " for the property: " + property.name);
  5180. }
  5181. } else if (property.isNavigationProperty) {
  5182. // sets navigation property: relatedDataProperties and dataProperty: relatedNavigationProperty
  5183. resolveFks(property);
  5184. // Tries to set - these two may get set later
  5185. // this.inverse
  5186. // this.entityType
  5187. updateCrossEntityRelationship(property);
  5188. }
  5189. };
  5190. ctor._getNormalizedTypeName = __memoize(function (rawTypeName) {
  5191. return rawTypeName && normalizeTypeName(rawTypeName).typeName;
  5192. });
  5193. // for debugging use the line below instead.
  5194. //ctor._getNormalizedTypeName = function (rawTypeName) { return normalizeTypeName(rawTypeName).typeName; };
  5195. proto._checkNavProperty = function (navigationProperty) {
  5196. if (navigationProperty.isNavigationProperty) {
  5197. if (navigationProperty.parentType != this) {
  5198. throw new Error(__formatString("The navigationProperty '%1' is not a property of entity type '%2'",
  5199. navigationProperty.name, this.name));
  5200. }
  5201. return navigationProperty;
  5202. }
  5203. if (typeof (navigationProperty) === 'string') {
  5204. var np = this.getProperty(navigationProperty);
  5205. if (np && np.isNavigationProperty) return np;
  5206. }
  5207. throw new Error("The 'navigationProperty' parameter must either be a NavigationProperty or the name of a NavigationProperty");
  5208. };
  5209. proto._addDataProperty = function (dp) {
  5210. this.dataProperties.push(dp);
  5211. if (dp.isPartOfKey) {
  5212. this.keyProperties.push(dp);
  5213. };
  5214. if (dp.isComplexProperty) {
  5215. this.complexProperties.push(dp);
  5216. }
  5217. if (dp.concurrencyMode && dp.concurrencyMode !== "None") {
  5218. this.concurrencyProperties.push(dp);
  5219. };
  5220. if (dp.isUnmapped) {
  5221. this.unmappedProperties.push(dp);
  5222. }
  5223. };
  5224. proto._addNavigationProperty = function (np) {
  5225. this.navigationProperties.push(np);
  5226. if (!isQualifiedTypeName(np.entityTypeName)) {
  5227. np.entityTypeName = qualifyTypeName(np.entityTypeName, this.namespace);
  5228. }
  5229. };
  5230. proto._fixup = function () {
  5231. var that = this;
  5232. this.getProperties().forEach(function (property) {
  5233. that._updateProperty(property);
  5234. });
  5235. updateIncomplete(this);
  5236. };
  5237. function updateIncomplete(entityType) {
  5238. var incompleteTypeMap = entityType.metadataStore._incompleteTypeMap;
  5239. var incompleteMap = incompleteTypeMap[entityType.name];
  5240. if (__isEmpty(incompleteMap)) {
  5241. delete incompleteTypeMap[entityType.name];
  5242. return;
  5243. }
  5244. if (incompleteMap) {
  5245. __objectForEach(incompleteMap, function (assocName, np) {
  5246. if (!np.entityType) {
  5247. if (np.entityTypeName === entityType.name) {
  5248. np.entityType = entityType;
  5249. delete incompleteMap[assocName];
  5250. updateIncomplete(np.parentType);
  5251. }
  5252. }
  5253. });
  5254. }
  5255. }
  5256. function resolveFks(np) {
  5257. if (np.foreignKeyProperties) return;
  5258. var fkProps = getFkProps(np);
  5259. // returns null if can't yet finish
  5260. if (!fkProps) return;
  5261. fkProps.forEach(function (dp) {
  5262. dp.relatedNavigationProperty = np;
  5263. np.parentType.foreignKeyProperties.push(dp);
  5264. if (np.relatedDataProperties) {
  5265. np.relatedDataProperties.push(dp);
  5266. } else {
  5267. np.relatedDataProperties = [dp];
  5268. }
  5269. });
  5270. };
  5271. // returns null if can't yet finish
  5272. function getFkProps(np) {
  5273. var fkNames = np.foreignKeyNames;
  5274. var isNameOnServer = fkNames.length == 0;
  5275. if (isNameOnServer) {
  5276. fkNames = np.foreignKeyNamesOnServer;
  5277. if (fkNames.length == 0) {
  5278. np.foreignKeyProperties = [];
  5279. return np.foreignKeyProperties;
  5280. }
  5281. }
  5282. var ok = true;
  5283. var parentEntityType = np.parentType;
  5284. var fkProps = fkNames.map(function (fkName) {
  5285. var fkProp = parentEntityType.getDataProperty(fkName, isNameOnServer);
  5286. ok = ok && !!fkProp;
  5287. return fkProp;
  5288. });
  5289. if (ok) {
  5290. if (isNameOnServer) {
  5291. np.foreignKeyNames = fkProps.map(__pluck("name"));
  5292. }
  5293. np.foreignKeyProperties = fkProps;
  5294. return fkProps;
  5295. } else {
  5296. return null;
  5297. }
  5298. }
  5299. function updateCrossEntityRelationship(np) {
  5300. var metadataStore = np.parentType.metadataStore;
  5301. var incompleteTypeMap = metadataStore._incompleteTypeMap;
  5302. // ok to not find it yet
  5303. var targetEntityType = metadataStore.getEntityType(np.entityTypeName, true);
  5304. if (targetEntityType) {
  5305. np.entityType = targetEntityType;
  5306. }
  5307. var assocMap = incompleteTypeMap[np.entityTypeName];
  5308. if (!assocMap) {
  5309. addToIncompleteMap(incompleteTypeMap, np);
  5310. } else {
  5311. var inverse = assocMap[np.associationName];
  5312. if (inverse) {
  5313. removeFromIncompleteMap(incompleteTypeMap, np, inverse);
  5314. } else {
  5315. addToIncompleteMap(incompleteTypeMap, np);
  5316. }
  5317. }
  5318. };
  5319. function addToIncompleteMap(incompleteTypeMap, np) {
  5320. if (!np.entityType) {
  5321. // Fixed based on this: http://stackoverflow.com/questions/14329352/bad-navigation-property-one-to-zero-or-one-relationship/14384399#14384399
  5322. var assocMap = incompleteTypeMap[np.entityTypeName];
  5323. if (!assocMap) {
  5324. assocMap = {};
  5325. incompleteTypeMap[np.entityTypeName] = assocMap;
  5326. }
  5327. assocMap[np.associationName] = np;
  5328. }
  5329. var altAssocMap = incompleteTypeMap[np.parentType.name];
  5330. if (!altAssocMap) {
  5331. altAssocMap = {};
  5332. incompleteTypeMap[np.parentType.name] = altAssocMap;
  5333. }
  5334. altAssocMap[np.associationName] = np;
  5335. }
  5336. function removeFromIncompleteMap(incompleteTypeMap, np, inverse) {
  5337. np.inverse = inverse;
  5338. var assocMap = incompleteTypeMap[np.entityTypeName];
  5339. delete assocMap[np.associationName];
  5340. if (__isEmpty(assocMap)) {
  5341. delete incompleteTypeMap[np.entityTypeName];
  5342. }
  5343. if (!inverse.inverse) {
  5344. inverse.inverse = np;
  5345. // not sure if these are needed
  5346. if (inverse.entityType == null) {
  5347. inverse.entityType = np.parentType;
  5348. }
  5349. var altAssocMap = incompleteTypeMap[np.parentType.name];
  5350. if (altAssocMap) {
  5351. delete altAssocMap[np.associationName];
  5352. if (__isEmpty(altAssocMap)) {
  5353. delete incompleteTypeMap[np.parentType.name];
  5354. }
  5355. }
  5356. }
  5357. }
  5358. function calcUnmappedProperties(entityType, instance) {
  5359. var metadataPropNames = entityType.getPropertyNames();
  5360. var trackablePropNames = __modelLibraryDef.getDefaultInstance().getTrackablePropertyNames(instance);
  5361. trackablePropNames.forEach(function (pn) {
  5362. if (metadataPropNames.indexOf(pn) == -1) {
  5363. var newProp = new DataProperty({
  5364. name: pn,
  5365. dataType: DataType.Undefined,
  5366. isNullable: true,
  5367. isUnmapped: true
  5368. });
  5369. entityType.addProperty(newProp);
  5370. }
  5371. });
  5372. }
  5373. return ctor;
  5374. })();
  5375. var ComplexType = (function () {
  5376. /**
  5377. Container for all of the metadata about a specific type of Complex object.
  5378. @class ComplexType
  5379. **/
  5380. /**
  5381. @example
  5382. var complexType = new ComplexType( {
  5383. shortName: "address",
  5384. namespace: "myAppNamespace"
  5385. });
  5386. @method <ctor> ComplexType
  5387. @param config {Object} Configuration settings
  5388. @param config.shortName {String}
  5389. @param [config.namespace=""] {String}
  5390. **/
  5391. var ctor = function (config) {
  5392. if (arguments.length > 1) {
  5393. throw new Error("The ComplexType ctor has a single argument that is a configuration object.");
  5394. }
  5395. assertConfig(config)
  5396. .whereParam("shortName").isNonEmptyString()
  5397. .whereParam("namespace").isString().isOptional().withDefault("")
  5398. .applyAll(this);
  5399. this.name = qualifyTypeName(this.shortName, this.namespace);
  5400. this.dataProperties = [];
  5401. this.complexProperties = [];
  5402. this.validators = [];
  5403. this.concurrencyProperties = [];
  5404. this.unmappedProperties = [];
  5405. };
  5406. var proto = ctor.prototype;
  5407. /**
  5408. The DataProperties (see {{#crossLink "DataProperty"}}{{/crossLink}}) associated with this ComplexType.
  5409. __readOnly__
  5410. @property dataProperties {Array of DataProperty}
  5411. **/
  5412. /**
  5413. The DataProperties for this ComplexType that contain instances of a ComplexType (see {{#crossLink "ComplexType"}}{{/crossLink}}).
  5414. __readOnly__
  5415. @property complexProperties {Array of DataProperty}
  5416. **/
  5417. /**
  5418. The DataProperties associated with this ComplexType that are not mapped to any backend datastore. These are effectively free standing
  5419. properties.
  5420. __readOnly__
  5421. @property unmappedProperties {Array of DataProperty}
  5422. **/
  5423. /**
  5424. The fully qualifed name of this ComplexType.
  5425. __readOnly__
  5426. @property name {String}
  5427. **/
  5428. /**
  5429. The short, unqualified, name for this ComplexType.
  5430. __readOnly__
  5431. @property shortName {String}
  5432. **/
  5433. /**
  5434. The namespace for this ComplexType.
  5435. __readOnly__
  5436. @property namespace {String}
  5437. **/
  5438. /**
  5439. The entity level validators associated with this ComplexType. Validators can be added and
  5440. removed from this collection.
  5441. __readOnly__
  5442. @property validators {Array of Validator}
  5443. **/
  5444. /**
  5445. Creates a new non-attached instance of this ComplexType.
  5446. @method createInstance
  5447. @param initialValues {Object} Configuration object containing initial values for the instance.
  5448. **/
  5449. proto.createInstance = function (initialValues) {
  5450. var instance = this._createInstanceCore();
  5451. if (initialValues) {
  5452. __objectForEach(initialValues, function (key, value) {
  5453. instance.setProperty(key, value);
  5454. });
  5455. }
  5456. instance.complexAspect._postInitialize();
  5457. return instance;
  5458. };
  5459. proto._createInstanceCore = function (parent, parentProperty ) {
  5460. var aCtor = this.getCtor();
  5461. var instance = new aCtor();
  5462. new ComplexAspect(instance, parent, parentProperty);
  5463. if (parent) {
  5464. instance.complexAspect._postInitialize();
  5465. }
  5466. return instance;
  5467. };
  5468. proto.addProperty = function (dataProperty) {
  5469. assertParam(dataProperty, "dataProperty").isInstanceOf(DataProperty).check();
  5470. if (this.metadataStore && ! dataProperty.isUnmapped) {
  5471. throw new Error("The '" + this.name + "' ComplexType has already been added to a MetadataStore and therefore no additional properties may be added to it.");
  5472. }
  5473. if (dataProperty.parentType) {
  5474. if (dataProperty.parentType !== this) {
  5475. throw new Error("This dataProperty has already been added to " + property.parentType.name);
  5476. } else {
  5477. return this;
  5478. }
  5479. }
  5480. this._addDataProperty(dataProperty);
  5481. return this;
  5482. };
  5483. proto.getProperties = function () {
  5484. return this.dataProperties;
  5485. };
  5486. /**
  5487. See {{#crossLink "EntityType.addValidator"}}{{/crossLink}}
  5488. @method addValidator
  5489. @param validator {Validator} Validator to add.
  5490. @param [property] Property to add this validator to. If omitted, the validator is assumed to be an
  5491. entity level validator and is added to the EntityType's 'validators'.
  5492. **/
  5493. /**
  5494. See {{#crossLink "EntityType.getProperty"}}{{/crossLink}}
  5495. @method getProperty
  5496. **/
  5497. /**
  5498. See {{#crossLink "EntityType.getPropertyNames"}}{{/crossLink}}
  5499. @method getPropertyNames
  5500. **/
  5501. /**
  5502. See {{#crossLink "EntityType.getEntityCtor"}}{{/crossLink}}
  5503. @method getCtor
  5504. **/
  5505. proto.addValidator = EntityType.prototype.addValidator;
  5506. proto.getProperty = EntityType.prototype.getProperty;
  5507. proto.getPropertyNames = EntityType.prototype.getPropertyNames;
  5508. proto._addDataProperty = EntityType.prototype._addDataProperty;
  5509. proto._updateProperty = EntityType.prototype._updateProperty;
  5510. // note the name change.
  5511. proto.getCtor = EntityType.prototype.getEntityCtor;
  5512. proto._setCtor = EntityType.prototype._setCtor;
  5513. proto.toJSON = function () {
  5514. return __toJson(this, ["dataProperties", "validators"]);
  5515. };
  5516. proto._fixup = function () {
  5517. var that = this;
  5518. this.dataProperties.forEach(function (property) {
  5519. that._updateProperty(property);
  5520. });
  5521. };
  5522. proto._$typeName = "ComplexType";
  5523. return ctor;
  5524. })();
  5525. var DataProperty = (function () {
  5526. /**
  5527. A DataProperty describes the metadata for a single property of an {{#crossLink "EntityType"}}{{/crossLink}} that contains simple data.
  5528. Instances of the DataProperty class are constructed automatically during Metadata retrieval. However it is also possible to construct them
  5529. directly via the constructor.
  5530. @class DataProperty
  5531. **/
  5532. /**
  5533. @example
  5534. var lastNameProp = new DataProperty( {
  5535. name: "lastName",
  5536. dataType: DataType.String,
  5537. isNullable: true,
  5538. maxLength: 20
  5539. });
  5540. // assuming personEntityType is a newly constructed EntityType
  5541. personEntityType.addProperty(lastNameProperty);
  5542. @method <ctor> DataProperty
  5543. @param config {configuration Object}
  5544. @param [config.name] {String} The name of this property.
  5545. @param [config.nameOnServer] {String} Same as above but the name is that defined on the server.
  5546. Either this or the 'name' above must be specified. Whichever one is specified the other will be computed using
  5547. the NamingConvention on the MetadataStore associated with the EntityType to which this will be added.
  5548. @param [config.dataType=DataType.String] {DataType}
  5549. @param [config.complexTypeName] {String}
  5550. @param [config.isNullable=true] {Boolean}
  5551. @param [config.defaultValue] {Any}
  5552. @param [config.isPartOfKey=false] {Boolean}
  5553. @param [config.isUnmapped=false] {Boolean}
  5554. @param [config.concurrencyMode] {String}
  5555. @param [config.maxLength] {Integer} Only meaningfull for DataType.String
  5556. @param [config.fixedLength] {Boolean} Only meaningfull for DataType.String
  5557. @param [config.validators] {Array of Validator}
  5558. **/
  5559. var ctor = function(config) {
  5560. assertConfig(config)
  5561. .whereParam("name").isString().isOptional()
  5562. .whereParam("nameOnServer").isString().isOptional()
  5563. .whereParam("dataType").isEnumOf(DataType).isOptional().or().isInstanceOf(ComplexType)
  5564. .whereParam("complexTypeName").isOptional()
  5565. .whereParam("isNullable").isBoolean().isOptional().withDefault(true)
  5566. .whereParam("defaultValue").isOptional()
  5567. .whereParam("isPartOfKey").isBoolean().isOptional()
  5568. .whereParam("isUnmapped").isBoolean().isOptional()
  5569. .whereParam("concurrencyMode").isString().isOptional()
  5570. .whereParam("maxLength").isNumber().isOptional()
  5571. .whereParam("fixedLength").isBoolean().isOptional()
  5572. .whereParam("validators").isInstanceOf(Validator).isArray().isOptional().withDefault([])
  5573. .whereParam("enumType").isOptional()
  5574. .whereParam("rawTypeName").isOptional() // occurs with undefined datatypes
  5575. .applyAll(this);
  5576. var hasName = !!(this.name || this.nameOnServer);
  5577. if (!hasName) {
  5578. throw new Error("A DataProperty must be instantiated with either a 'name' or a 'nameOnServer' property");
  5579. }
  5580. if (this.complexTypeName) {
  5581. this.isComplexProperty = true;
  5582. } else if (!this.dataType) {
  5583. this.dataType = DataType.String;
  5584. }
  5585. // == as opposed to === is deliberate here.
  5586. if (this.defaultValue == null) {
  5587. if (this.isNullable) {
  5588. this.defaultValue = null;
  5589. } else {
  5590. if (this.isComplexProperty) {
  5591. // what to do? - shouldn't happen from EF - but otherwise ???
  5592. } else if (this.dataType === DataType.Binary) {
  5593. this.defaultValue = "AAAAAAAAJ3U="; // hack for all binary fields but value is specifically valid for timestamp fields - arbitrary valid 8 byte base64 value.
  5594. } else {
  5595. this.defaultValue = this.dataType.defaultValue;
  5596. if (this.defaultValue == null) {
  5597. throw new Error("A nonnullable DataProperty cannot have a null defaultValue. Name: " + this.name);
  5598. }
  5599. }
  5600. }
  5601. }
  5602. };
  5603. var proto = ctor.prototype;
  5604. proto._$typeName = "DataProperty";
  5605. /**
  5606. The name of this property
  5607. __readOnly__
  5608. @property name {String}
  5609. **/
  5610. /**
  5611. The parent type that this property belongs to - will be either a {{#crossLink "EntityType"}}{{/crossLink}} or a {{#crossLink "ComplexType"}}{{/crossLink}}.
  5612. __readOnly__
  5613. @property parentType {EntityType|ComplexType}
  5614. **/
  5615. /**
  5616. The {{#crossLink "DataType"}}{{/crossLink}} of this property.
  5617. __readOnly__
  5618. @property dataType {DataType}
  5619. **/
  5620. /**
  5621. The name of the {{#crossLink "ComplexType"}}{{/crossLink}} associated with this property; may be null.
  5622. __readOnly__
  5623. @property complexTypeName {String}
  5624. **/
  5625. /**
  5626. Whether the contents of this property is an instance of a {{#crossLink "ComplexType"}}{{/crossLink}}.
  5627. __readOnly__
  5628. @property isComplexProperty {bool}
  5629. **/
  5630. /**
  5631. Whether this property is nullable.
  5632. __readOnly__
  5633. @property isNullable {Boolean}
  5634. **/
  5635. /**
  5636. Whether this property is a 'key' property.
  5637. __readOnly__
  5638. @property isPartOfKey {Boolean}
  5639. **/
  5640. /**
  5641. Whether this property is an 'unmapped' property.
  5642. __readOnly__
  5643. @property isUnmapped {Boolean}
  5644. **/
  5645. /**
  5646. __Describe this__
  5647. __readOnly__
  5648. @property concurrencyMode {String}
  5649. **/
  5650. /**
  5651. The maximum length for the value of this property.
  5652. __readOnly__
  5653. @property maxLength {Number}
  5654. **/
  5655. /**
  5656. Whether this property is of 'fixed' length or not.
  5657. __readOnly__
  5658. @property fixedLength {Boolean}
  5659. **/
  5660. /**
  5661. The {{#crossLink "Validator"}}{{/crossLink}}s that are associated with this property. Validators can be added and
  5662. removed from this collection.
  5663. __readOnly__
  5664. @property validators {Array of Validator}
  5665. **/
  5666. /**
  5667. The default value for this property.
  5668. __readOnly__
  5669. @property defaultValue {any}
  5670. **/
  5671. /**
  5672. The navigation property related to this property. Will only be set if this is a foreign key property.
  5673. __readOnly__
  5674. @property relatedNavigationProperty {NavigationProperty}
  5675. **/
  5676. /**
  5677. Is this a DataProperty? - always true here
  5678. Allows polymorphic treatment of DataProperties and NavigationProperties.
  5679. __readOnly__
  5680. @property isDataProperty {Boolean}
  5681. **/
  5682. /**
  5683. Is this a NavigationProperty? - always false here
  5684. Allows polymorphic treatment of DataProperties and NavigationProperties.
  5685. __readOnly__
  5686. @property isNavigationProperty {Boolean}
  5687. **/
  5688. proto.isDataProperty = true;
  5689. proto.isNavigationProperty = false;
  5690. proto.toJSON = function () {
  5691. var json = __toJson(this, ["validators"]);
  5692. delete json.isComplexProperty;
  5693. return json;
  5694. };
  5695. ctor.fromJSON = function (json, parentEntityType) {
  5696. json.dataType = DataType.fromName(json.dataType);
  5697. json.validators = json.validators.map(Validator.fromJSON);
  5698. var dp = new DataProperty(json);
  5699. parentEntityType.addProperty(dp);
  5700. return dp;
  5701. };
  5702. return ctor;
  5703. })();
  5704. var NavigationProperty = (function () {
  5705. /**
  5706. A NavigationProperty describes the metadata for a single property of an {{#crossLink "EntityType"}}{{/crossLink}} that return instances of other EntityTypes.
  5707. Instances of the NavigationProperty class are constructed automatically during Metadata retrieval. However it is also possible to construct them
  5708. directly via the constructor.
  5709. @class NavigationProperty
  5710. **/
  5711. /**
  5712. @example
  5713. var homeAddressProp = new NavigationProperty( {
  5714. name: "homeAddress",
  5715. entityTypeName: "Address:#myNamespace",
  5716. isScalar: true,
  5717. associationName: "address_person",
  5718. foreignKeyNames: ["homeAddressId"]
  5719. });
  5720. var homeAddressIdProp = new DataProperty( {
  5721. name: "homeAddressId"
  5722. dataType: DataType.Integer
  5723. });
  5724. // assuming personEntityType is a newly constructed EntityType
  5725. personEntityType.addProperty(homeAddressProp);
  5726. personEntityType.addProperty(homeAddressIdProp);
  5727. @method <ctor> NavigationProperty
  5728. @param config {configuration Object}
  5729. @param [config.name] {String} The name of this property.
  5730. @param [config.nameOnServer] {String} Same as above but the name is that defined on the server.
  5731. Either this or the 'name' above must be specified. Whichever one is specified the other will be computed using
  5732. the NamingConvention on the MetadataStore associated with the EntityType to which this will be added.
  5733. @param config.entityTypeName {String} The fully qualified name of the type of entity that this property will return. This type
  5734. need not yet have been created, but it will need to get added to the relevant MetadataStore before this EntityType will be 'complete'.
  5735. The entityType name is constructed as: {shortName} + ":#" + {namespace}
  5736. @param [config.isScalar] {Boolean}
  5737. @param [config.associationName] {String} A name that will be used to connect the two sides of a navigation. May be omitted for unidirectional navigations.
  5738. @param [config.foreignKeyNames] {Array of String} An array of foreign key names. The array is needed to support the possibility of multipart foreign keys.
  5739. Most of the time this will be a single foreignKeyName in an array.
  5740. @param [config.foreignKeyNamesOnServer] {Array of String} Same as above but the names are those defined on the server. Either this or 'foreignKeyNames' must
  5741. be specified, if there are foreignKeys. Whichever one is specified the other will be computed using
  5742. the NamingConvention on the MetadataStore associated with the EntityType to which this will be added.
  5743. @param [config.validators] {Array of Validator}
  5744. **/
  5745. var ctor = function(config) {
  5746. assertConfig(config)
  5747. .whereParam("name").isString().isOptional()
  5748. .whereParam("nameOnServer").isString().isOptional()
  5749. .whereParam("entityTypeName").isString()
  5750. .whereParam("isScalar").isBoolean()
  5751. .whereParam("associationName").isString().isOptional()
  5752. .whereParam("foreignKeyNames").isArray().isString().isOptional().withDefault([])
  5753. .whereParam("foreignKeyNamesOnServer").isArray().isString().isOptional().withDefault([])
  5754. .whereParam("validators").isInstanceOf(Validator).isArray().isOptional().withDefault([])
  5755. .applyAll(this);
  5756. var hasName = !!(this.name || this.nameOnServer);
  5757. if (!hasName) {
  5758. throw new Error("A Navigation property must be instantiated with either a 'name' or a 'nameOnServer' property");
  5759. }
  5760. };
  5761. var proto = ctor.prototype;
  5762. proto._$typeName = "NavigationProperty";
  5763. /**
  5764. The {{#crossLink "EntityType"}}{{/crossLink}} that this property belongs to.
  5765. __readOnly__
  5766. @property parentEntityType {EntityType}
  5767. **/
  5768. /**
  5769. The name of this property
  5770. __readOnly__
  5771. @property name {String}
  5772. **/
  5773. /**
  5774. The {{#crossLink "EntityType"}}{{/crossLink}} returned by this property.
  5775. __readOnly__
  5776. @property entityType {EntityType}
  5777. **/
  5778. /**
  5779. Whether this property returns a single entity or an array of entities.
  5780. __readOnly__
  5781. @property isScalar {Boolean}
  5782. **/
  5783. /**
  5784. The name of the association to which that this property belongs. This associationName will be shared with this
  5785. properties 'inverse'.
  5786. __readOnly__
  5787. @property associationName {String}
  5788. **/
  5789. /**
  5790. The names of the foreign key DataProperties associated with this NavigationProperty. There will usually only be a single DataProperty associated
  5791. with a Navigation property except in the case of entities with multipart keys.
  5792. __readOnly__
  5793. @property foreignKeyNames {Array of String}
  5794. **/
  5795. /**
  5796. The 'foreign key' DataProperties associated with this NavigationProperty. There will usually only be a single DataProperty associated
  5797. with a Navigation property except in the case of entities with multipart keys.
  5798. __readOnly__
  5799. @property relatedDataProperties {Array of DataProperty}
  5800. **/
  5801. /**
  5802. The inverse of this NavigationProperty. The NavigationProperty that represents a navigation in the opposite direction
  5803. to this NavigationProperty.
  5804. __readOnly__
  5805. @property inverse {NavigationProperty}
  5806. **/
  5807. /**
  5808. The {{#crossLink "Validator"}}{{/crossLink}}s that are associated with this property. Validators can be added and
  5809. removed from this collection.
  5810. __readOnly__
  5811. @property validators {Array of Validator}
  5812. **/
  5813. /**
  5814. Is this a DataProperty? - always false here
  5815. Allows polymorphic treatment of DataProperties and NavigationProperties.
  5816. __readOnly__
  5817. @property isDataProperty {Boolean}
  5818. **/
  5819. /**
  5820. Is this a NavigationProperty? - always true here
  5821. Allows polymorphic treatment of DataProperties and NavigationProperties.
  5822. __readOnly__
  5823. @property isNavigationProperty {Boolean}
  5824. **/
  5825. proto.isDataProperty = false;
  5826. proto.isNavigationProperty = true;
  5827. proto.toJSON = function () {
  5828. return __toJson(this, ["validators", "foreignKeyNames", "foreignKeyNamesOnServer"]);
  5829. };
  5830. ctor.fromJSON = function (json, parentEntityType) {
  5831. json.validators = json.validators.map(Validator.fromJSON);
  5832. var np = new NavigationProperty(json);
  5833. parentEntityType.addProperty(np);
  5834. return np;
  5835. };
  5836. return ctor;
  5837. })();
  5838. var AutoGeneratedKeyType = function () {
  5839. /**
  5840. AutoGeneratedKeyType is an 'Enum' containing all of the valid states for an automatically generated key.
  5841. @class AutoGeneratedKeyType
  5842. @static
  5843. @final
  5844. **/
  5845. var ctor = new Enum("AutoGeneratedKeyType");
  5846. /**
  5847. This entity does not have an autogenerated key.
  5848. The client must set the key before adding the entity to the EntityManager
  5849. @property None {AutoGeneratedKeyType}
  5850. @final
  5851. @static
  5852. **/
  5853. ctor.None = ctor.addSymbol();
  5854. /**
  5855. This entity's key is an Identity column and is set by the backend database.
  5856. Keys for new entities will be temporary until the entities are saved at which point the keys will
  5857. be converted to their 'real' versions.
  5858. @property Identity {AutoGeneratedKeyType}
  5859. @final
  5860. @static
  5861. **/
  5862. ctor.Identity = ctor.addSymbol();
  5863. /**
  5864. This entity's key is generated by a KeyGenerator and is set by the backend database.
  5865. Keys for new entities will be temporary until the entities are saved at which point the keys will
  5866. be converted to their 'real' versions.
  5867. @property KeyGenerator {AutoGeneratedKeyType}
  5868. @final
  5869. @static
  5870. **/
  5871. ctor.KeyGenerator = ctor.addSymbol();
  5872. ctor.seal();
  5873. return ctor;
  5874. }();
  5875. // mixin methods
  5876. (function() {
  5877. var proto = Param.prototype;
  5878. proto.isEntity = function() {
  5879. return this._addContext({
  5880. fn: isEntity,
  5881. msg: " must be an entity"
  5882. });
  5883. };
  5884. function isEntity(context, v) {
  5885. if (v == null) return false;
  5886. return (v.entityType !== undefined);
  5887. }
  5888. proto.isEntityProperty = function() {
  5889. return this._addContext({
  5890. fn: isEntityProperty,
  5891. msg: " must be either a DataProperty or a NavigationProperty"
  5892. });
  5893. };
  5894. function isEntityProperty(context, v) {
  5895. if (v == null) return false;
  5896. return (v.isDataProperty || v.isNavigationProperty);
  5897. }
  5898. })();
  5899. function isQualifiedTypeName(entityTypeName) {
  5900. return entityTypeName.indexOf(":#") >= 0;
  5901. }
  5902. function qualifyTypeName(simpleTypeName, namespace) {
  5903. return simpleTypeName + ":#" + namespace;
  5904. }
  5905. // schema is only needed for navProperty type name
  5906. function normalizeTypeName(entityTypeName, schema) {
  5907. if (!entityTypeName) {
  5908. return null;
  5909. }
  5910. if (__stringStartsWith(entityTypeName, MetadataStore.ANONTYPE_PREFIX)) {
  5911. return {
  5912. shortTypeName: entityTypeName,
  5913. namespace: "",
  5914. typeName: entityTypeName,
  5915. isAnon: true
  5916. };
  5917. }
  5918. var entityTypeNameNoAssembly = entityTypeName.split(",")[0];
  5919. var nameParts = entityTypeNameNoAssembly.split(".");
  5920. if (nameParts.length > 1) {
  5921. var simpleTypeName = nameParts[nameParts.length - 1];
  5922. var ns;
  5923. if (schema) {
  5924. ns = getNamespaceFor(simpleTypeName, schema);
  5925. } else {
  5926. var namespaceParts = nameParts.slice(0, nameParts.length - 1);
  5927. ns = namespaceParts.join(".");
  5928. }
  5929. return {
  5930. shortTypeName: simpleTypeName,
  5931. namespace: ns,
  5932. typeName: qualifyTypeName(simpleTypeName, ns)
  5933. };
  5934. } else {
  5935. return {
  5936. shortTypeName: entityTypeName,
  5937. namespace: "",
  5938. typeName: entityTypeName
  5939. };
  5940. }
  5941. }
  5942. function getNamespaceFor(shortName, schema) {
  5943. var ns;
  5944. var mapping = schema.cSpaceOSpaceMapping;
  5945. if (mapping) {
  5946. var fullName = mapping[schema.namespace + "." + shortName];
  5947. ns = fullName && fullName.substr(0, fullName.length - (shortName.length + 1));
  5948. }
  5949. return ns || schema.namespace;
  5950. }
  5951. breeze.MetadataStore= MetadataStore;
  5952. breeze.DataService= DataService;
  5953. breeze.JsonResultsAdapter = JsonResultsAdapter;
  5954. breeze.EntityType = EntityType;
  5955. breeze.ComplexType = ComplexType;
  5956. breeze.DataProperty= DataProperty;
  5957. breeze.NavigationProperty = NavigationProperty;
  5958. breeze.DataType = DataType;
  5959. breeze.AutoGeneratedKeyType = AutoGeneratedKeyType;
  5960. breeze.NamingConvention = NamingConvention;
  5961. var EntityQuery = (function () {
  5962. /**
  5963. An EntityQuery instance is used to query entities either from a remote datasource or from a local {{#crossLink "EntityManager"}}{{/crossLink}}.
  5964. EntityQueries are immutable - this means that all EntityQuery methods that return an EntityQuery actually create a new EntityQuery. This means that
  5965. EntityQueries can be 'modified' without affecting any current instances.
  5966. @class EntityQuery
  5967. **/
  5968. /**
  5969. @example
  5970. var query = new EntityQuery("Customers")
  5971. Usually this constructor will be followed by calls to filtering, ordering or selection methods
  5972. @example
  5973. var query = new EntityQuery("Customers")
  5974. .where("CompanyName", "startsWith", "C")
  5975. .orderBy("Region");
  5976. @method <ctor> EntityQuery
  5977. @param [resourceName] {String}
  5978. **/
  5979. var ctor = function (resourceName) {
  5980. assertParam(resourceName, "resourceName").isOptional().isString().check();
  5981. this.resourceName = normalizeResourceName(resourceName);
  5982. this.entityType = null;
  5983. this.wherePredicate = null;
  5984. this.orderByClause = null;
  5985. this.selectClause = null;
  5986. this.skipCount = null;
  5987. this.takeCount = null;
  5988. this.expandClause = null;
  5989. this.parameters = {};
  5990. this.inlineCountEnabled = false;
  5991. // default is to get queryOptions from the entityManager.
  5992. this.queryOptions = null;
  5993. this.entityManager = null;
  5994. this.dataService = null;
  5995. };
  5996. var proto = ctor.prototype;
  5997. /**
  5998. The resource name used by this query.
  5999. __readOnly__
  6000. @property resourceName {String}
  6001. **/
  6002. /**
  6003. The 'where' predicate used by this query.
  6004. __readOnly__
  6005. @property wherePredicate {Predicate}
  6006. **/
  6007. /**
  6008. The {{#crossLink "OrderByClause"}}{{/crossLink}} used by this query.
  6009. __readOnly__
  6010. @property orderByClause {OrderByClause}
  6011. **/
  6012. /**
  6013. The number of entities to 'skip' for this query.
  6014. __readOnly__
  6015. @property skipCount {Integer}
  6016. **/
  6017. /**
  6018. The number of entities to 'take' for this query.
  6019. __readOnly__
  6020. @property takeCount {Integer}
  6021. **/
  6022. /**
  6023. Any additional parameters that were added to the query via the 'withParameters' method.
  6024. __readOnly__
  6025. @property parameters {Object}
  6026. **/
  6027. /**
  6028. The {{#crossLink "QueryOptions"}}{{/crossLink}} for this query.
  6029. __readOnly__
  6030. @property queryOptions {QueryOptions}
  6031. **/
  6032. /**
  6033. The {{#crossLink "EntityManager"}}{{/crossLink}} for this query. This may be null and can be set via the 'using' method.
  6034. __readOnly__
  6035. @property entityManager {EntityManager}
  6036. **/
  6037. /**
  6038. Specifies the resource to query for this EntityQuery.
  6039. @example
  6040. var query = new EntityQuery()
  6041. .from("Customers");
  6042. is the same as
  6043. @example
  6044. var query = new EntityQuery("Customers");
  6045. @method from
  6046. @param resourceName {String} The resource to query.
  6047. @return {EntityQuery}
  6048. @chainable
  6049. **/
  6050. proto.from = function (resourceName) {
  6051. // TODO: think about allowing entityType as well
  6052. assertParam(resourceName, "resourceName").isString().check();
  6053. resourceName = normalizeResourceName(resourceName);
  6054. var currentName = this.resourceName;
  6055. if (currentName && currentName !== resourceName) {
  6056. throw new Error("This query already has an resourceName - the resourceName may only be set once per query");
  6057. }
  6058. var eq = this._clone();
  6059. eq.resourceName = resourceName;
  6060. return eq;
  6061. };
  6062. /**
  6063. This is a static version of the "from" method and it creates a 'base' entityQuery for the specified resource name.
  6064. @example
  6065. var query = EntityQuery.from("Customers");
  6066. is the same as
  6067. @example
  6068. var query = new EntityQuery("Customers");
  6069. @method from
  6070. @static
  6071. @param resourceName {String} The resource to query.
  6072. @return {EntityQuery}
  6073. @chainable
  6074. **/
  6075. ctor.from = function (resourceName) {
  6076. assertParam(resourceName, "resourceName").isString().check();
  6077. return new EntityQuery(resourceName);
  6078. };
  6079. /**
  6080. Specifies the top level EntityType that this query will return. Only needed when a query returns a json result that does not include type information.
  6081. @example
  6082. var query = new EntityQuery()
  6083. .from("MyCustomMethod")
  6084. .toType("Customer")
  6085. @method toType
  6086. @param entityType {String|EntityType} The top level entityType that this query will return. This method is only needed when a query returns a json result that
  6087. does not include type information. If the json result consists of more than a simple entity or array of entities, consider using a JsonResultsAdapter instead.
  6088. @return {EntityQuery}
  6089. @chainable
  6090. **/
  6091. proto.toType = function(entityType) {
  6092. assertParam(entityType, "entityType").isString().or.isInstanceOf(EntityType).check();
  6093. var eq = this._clone();
  6094. eq.toEntityType = entityType;
  6095. };
  6096. /**
  6097. Returns a new query with an added filter criteria. Can be called multiple times which means to 'and' with any existing Predicate.
  6098. @example
  6099. var query = new EntityQuery("Customers")
  6100. .where("CompanyName", "startsWith", "C");
  6101. This can also be expressed using an explicit {{#crossLink "FilterQueryOp"}}{{/crossLink}} as
  6102. @example
  6103. var query = new EntityQuery("Customers")
  6104. .where("CompanyName", FilterQueryOp.StartsWith, "C");
  6105. or a preconstructed {{#crossLink "Predicate"}}{{/crossLink}} may be used
  6106. @example
  6107. var pred = new Predicate("CompanyName", FilterQueryOp.StartsWith, "C");
  6108. var query = new EntityQuery("Customers")
  6109. .where(pred);
  6110. Predicates are often useful when you want to combine multiple conditions in a single filter, such as
  6111. @example
  6112. var pred = Predicate.create("CompanyName", "startswith", "C").and("Region", FilterQueryOp.Equals, null);
  6113. var query = new EntityQuery("Customers")
  6114. .where(pred);
  6115. @example
  6116. More complicated queries can make use of nested property paths
  6117. @example
  6118. var query = new EntityQuery("Products")
  6119. .where("Category.CategoryName", "startswith", "S");
  6120. or OData functions - A list of valid OData functions can be found within the {{#crossLink "Predicate"}}{{/crossLink}} documentation.
  6121. @example
  6122. var query = new EntityQuery("Customers")
  6123. .where("toLower(CompanyName)", "startsWith", "c");
  6124. or to be even more baroque
  6125. @example
  6126. var query = new EntityQuery("Customers")
  6127. .where("toUpper(substring(CompanyName, 1, 2))", FilterQueryOp.Equals, "OM");
  6128. @method where
  6129. @param predicate {Predicate|property|property path, operator, value} Can be either
  6130. - a single {{#crossLink "Predicate"}}{{/crossLink}}
  6131. - or the parameters to create a 'simple' Predicate
  6132. - a property name, a property path with '.' as path seperators or a property expression {String}
  6133. - an operator {FilterQueryOp|String} Either a {{#crossLink "FilterQueryOp"}}{{/crossLink}} or it's string representation. Case is ignored
  6134. when if a string is provided and any string that matches one of the FilterQueryOp aliases will be accepted.
  6135. - a value {Object} - This will be treated as either a property expression or a literal depending on context. In general,
  6136. if the value can be interpreted as a property expression it will be, otherwise it will be treated as a literal.
  6137. In most cases this works well, but you can also force the interpretation by setting the next parameter 'valueIsLiteral' to true.
  6138. - an optional [valueIsLiteral] {Boolean} parameter - Used to force the 'value' parameter to be treated as a literal - otherwise this will be inferred based on the context.
  6139. @return {EntityQuery}
  6140. @chainable
  6141. **/
  6142. proto.where = function (predicate) {
  6143. var eq = this._clone();
  6144. if (arguments.length === 0) {
  6145. eq.wherePredicate = null;
  6146. return eq;
  6147. }
  6148. var pred;
  6149. if (Predicate.isPredicate(predicate)) {
  6150. pred = predicate;
  6151. } else {
  6152. pred = Predicate.create(Array.prototype.slice.call(arguments));
  6153. }
  6154. if (eq.entityType) pred.validate(eq.entityType);
  6155. if (eq.wherePredicate) {
  6156. eq.wherePredicate = new CompositePredicate('and', [eq.wherePredicate, pred]);
  6157. } else {
  6158. eq.wherePredicate = pred;
  6159. }
  6160. return eq;
  6161. };
  6162. /**
  6163. Returns a new query that orders the results of the query by property name. By default sorting occurs is ascending order, but sorting in descending order is supported as well.
  6164. @example
  6165. var query = new EntityQuery("Customers")
  6166. .orderBy("CompanyName");
  6167. or to sort across multiple properties
  6168. @example
  6169. var query = new EntityQuery("Customers")
  6170. .orderBy("Region, CompanyName");
  6171. Nested property paths are also supported
  6172. @example
  6173. var query = new EntityQuery("Products")
  6174. .orderBy("Category.CategoryName");
  6175. Sorting in descending order is supported via the addition of ' desc' to the end of any property path.
  6176. @example
  6177. var query = new EntityQuery("Customers")
  6178. .orderBy("CompanyName desc");
  6179. or
  6180. @example
  6181. var query = new EntityQuery("Customers")
  6182. .orderBy("Region desc, CompanyName desc");
  6183. @method orderBy
  6184. @param propertyPaths {String|Array of String} A comma-separated (',') string of property paths or an array of property paths. Each property path can optionally end with " desc" to force a descending sort order.
  6185. @return {EntityQuery}
  6186. @chainable
  6187. **/
  6188. proto.orderBy = function (propertyPaths) {
  6189. // deliberately don't pass in isDesc
  6190. return orderByCore(this, normalizePropertyPaths(propertyPaths));
  6191. };
  6192. /**
  6193. Returns a new query that orders the results of the query by property name in descending order.
  6194. @example
  6195. var query = new EntityQuery("Customers")
  6196. .orderByDesc("CompanyName");
  6197. or to sort across multiple properties
  6198. @example
  6199. var query = new EntityQuery("Customers")
  6200. .orderByDesc("Region, CompanyName");
  6201. Nested property paths are also supported
  6202. @example
  6203. var query = new EntityQuery("Products")
  6204. .orderByDesc("Category.CategoryName");
  6205. @method orderByDesc
  6206. @param propertyPaths {String|Array of String} A comma-separated (',') string of property paths or an array of property paths.
  6207. @return {EntityQuery}
  6208. @chainable
  6209. **/
  6210. proto.orderByDesc = function (propertyPaths) {
  6211. return orderByCore(this, normalizePropertyPaths(propertyPaths), true);
  6212. };
  6213. /**
  6214. Returns a new query that selects a list of properties from the results of the original query and returns the values of just these properties. This
  6215. will be referred to as a projection.
  6216. If the result of this selection "projection" contains entities, these entities will automatically be added to EntityManager's cache and will
  6217. be made 'observable'.
  6218. Any simple properties, i.e. strings, numbers or dates within a projection will not be cached are will NOT be made 'observable'.
  6219. @example
  6220. Simple data properties can be projected
  6221. @example
  6222. var query = new EntityQuery("Customers")
  6223. .where("CompanyName", "startsWith", "C")
  6224. .select("CompanyName");
  6225. This will return an array of objects each with a single "CompanyName" property of type string.
  6226. A similar query could return a navigation property instead
  6227. @example
  6228. var query = new EntityQuery("Customers")
  6229. .where("CompanyName", "startsWith", "C")
  6230. .select("Orders");
  6231. where the result would be an array of objects each with a single "Orders" property that would itself be an array of "Order" entities.
  6232. Composite projections are also possible:
  6233. @example
  6234. var query = new EntityQuery("Customers")
  6235. .where("CompanyName", "startsWith", "C")
  6236. .select("CompanyName, Orders");
  6237. As well as projections involving nested property paths
  6238. @example
  6239. var query = EntityQuery("Orders")
  6240. .where("Customer.CompanyName", "startsWith", "C")
  6241. .select("Customer.CompanyName, Customer, OrderDate");
  6242. @method select
  6243. @param propertyPaths {String|Array of String} A comma-separated (',') string of property paths or an array of property paths.
  6244. @return {EntityQuery}
  6245. @chainable
  6246. **/
  6247. proto.select = function (propertyPaths) {
  6248. return selectCore(this, normalizePropertyPaths(propertyPaths));
  6249. };
  6250. /**
  6251. Returns a new query that skips the specified number of entities when returning results.
  6252. @example
  6253. var query = new EntityQuery("Customers")
  6254. .where("CompanyName", "startsWith", "C")
  6255. .skip(5);
  6256. @method skip
  6257. @param count {Number} The number of entities to return. If omitted this clears the
  6258. @return {EntityQuery}
  6259. @chainable
  6260. **/
  6261. proto.skip = function (count) {
  6262. assertParam(count, "count").isOptional().isNumber().check();
  6263. var eq = this._clone();
  6264. if (arguments.length === 0) {
  6265. eq.skipCount = null;
  6266. } else {
  6267. eq.skipCount = count;
  6268. }
  6269. return eq;
  6270. };
  6271. /**
  6272. Returns a new query that returns only the specified number of entities when returning results. - Same as 'take'.
  6273. @example
  6274. var query = new EntityQuery("Customers")
  6275. .top(5);
  6276. @method top
  6277. @param count {Number} The number of entities to return.
  6278. @return {EntityQuery}
  6279. @chainable
  6280. **/
  6281. proto.top = function(count) {
  6282. return this.take(count);
  6283. };
  6284. /**
  6285. Returns a new query that returns only the specified number of entities when returning results - Same as 'top'
  6286. @example
  6287. var query = new EntityQuery("Customers")
  6288. .take(5);
  6289. @method take
  6290. @param count {Number} The number of entities to return.
  6291. @return {EntityQuery}
  6292. @chainable
  6293. **/
  6294. proto.take = function (count) {
  6295. assertParam(count, "count").isOptional().isNumber().check();
  6296. var eq = this._clone();
  6297. if (arguments.length === 0) {
  6298. eq.takeCount = null;
  6299. } else {
  6300. eq.takeCount = count;
  6301. }
  6302. return eq;
  6303. };
  6304. /**
  6305. Returns a new query that will return related entities nested within its results. The expand method allows you to identify related entities, via navigation property
  6306. names such that a graph of entities may be retrieved with a single request. Any filtering occurs before the results are 'expanded'.
  6307. @example
  6308. var query = new EntityQuery("Customers")
  6309. .where("CompanyName", "startsWith", "C")
  6310. .expand("Orders");
  6311. will return the filtered customers each with its "Orders" properties fully resolved.
  6312. Multiple paths may be specified by separating the paths by a ','
  6313. @example
  6314. var query = new EntityQuery("Orders")
  6315. .expand("Customer, Employee")
  6316. and nested property paths my be specified as well
  6317. @example
  6318. var query = new EntityQuery("Orders")
  6319. .expand("Customer, OrderDetails, OrderDetails.Product")
  6320. @method expand
  6321. @param propertyPaths {String|Array of String} A comma-separated list of navigation property names or an array of navigation property names. Each Navigation Property name can be followed
  6322. by a '.' and another navigation property name to enable identifying a multi-level relationship
  6323. @return {EntityQuery}
  6324. @chainable
  6325. **/
  6326. proto.expand = function (propertyPaths) {
  6327. return expandCore(this, normalizePropertyPaths(propertyPaths));
  6328. };
  6329. /**
  6330. Returns a new query that includes a collection of parameters to pass to the server.
  6331. @example
  6332. var query = EntityQuery.from("EmployeesFilteredByCountryAndBirthdate")
  6333. .withParameters({ BirthDate: "1/1/1960", Country: "USA" });
  6334. will call the 'EmployeesFilteredByCountryAndBirthdata' method on the server and pass in 2 parameters. This
  6335. query will be uri encoded as
  6336. {serviceApi}/EmployeesFilteredByCountryAndBirthdate?birthDate=1%2F1%2F1960&country=USA
  6337. Parameters may also be mixed in with other query criteria.
  6338. @example
  6339. var query = EntityQuery.from("EmployeesFilteredByCountryAndBirthdate")
  6340. .withParameters({ BirthDate: "1/1/1960", Country: "USA" })
  6341. .where("LastName", "startsWith", "S")
  6342. .orderBy("BirthDate");
  6343. @method withParameters
  6344. @param parameters {Object} A parameters object where the keys are the parameter names and the values are the parameter values.
  6345. @return {EntityQuery}
  6346. @chainable
  6347. **/
  6348. proto.withParameters = function(parameters) {
  6349. assertParam(parameters, "parameters").isObject().check();
  6350. return withParametersCore(this, parameters);
  6351. };
  6352. /**
  6353. Returns a query with the 'inlineCount' capability either enabled or disabled. With 'inlineCount' enabled, an additional 'inlineCount' property
  6354. will be returned with the query results that will contain the number of entities that would have been returned by this
  6355. query with only the 'where'/'filter' clauses applied, i.e. without any 'skip'/'take' operators applied. For local queries this clause is ignored.
  6356. @example
  6357. var query = new EntityQuery("Customers")
  6358. .take(20)
  6359. .orderBy("CompanyName")
  6360. .inlineCount(true);
  6361. will return the first 20 customers as well as a count of all of the customers in the remote store.
  6362. @method inlineCount
  6363. @param enabled {Boolean=true} Whether or not inlineCount capability should be enabled. If this parameter is omitted, true is assumed.
  6364. @return {EntityQuery}
  6365. @chainable
  6366. **/
  6367. proto.inlineCount = function(enabled) {
  6368. if (enabled === undefined) enabled = true;
  6369. var eq = this._clone();
  6370. eq.inlineCountEnabled = enabled;
  6371. return eq;
  6372. };
  6373. /**
  6374. Returns a copy of this EntityQuery with the specified {{#crossLink "EntityManager"}}{{/crossLink}}, {{#crossLink "DataService"}}{{/crossLink}},
  6375. {{#crossLink "JsonResultsAdapter"}}{{/crossLink}}, {{#crossLink "MergeStrategy"}}{{/crossLink}} or {{#crossLink "FetchStrategy"}}{{/crossLink}} applied.
  6376. @example
  6377. 'using' can be used to return a new query with a specified EntityManager.
  6378. @example
  6379. var em = new EntityManager(serviceName);
  6380. var query = new EntityQuery("Orders")
  6381. .using(em);
  6382. or with a specified {{#crossLink "MergeStrategy"}}{{/crossLink}}
  6383. @example
  6384. var em = new EntityManager(serviceName);
  6385. var query = new EntityQuery("Orders")
  6386. .using(MergeStrategy.PreserveChanges);
  6387. or with a specified {{#crossLink "FetchStrategy"}}{{/crossLink}}
  6388. @example
  6389. var em = new EntityManager(serviceName);
  6390. var query = new EntityQuery("Orders")
  6391. .using(FetchStrategy.FromLocalCache);
  6392. @example
  6393. @method using
  6394. @param obj {EntityManager|MergeStrategy|FetchStrategy|DataService|JsonResultsAdapter} The object to update in creating a new EntityQuery from an existing one.
  6395. @return {EntityQuery}
  6396. @chainable
  6397. **/
  6398. proto.using = function (obj) {
  6399. var eq = this._clone();
  6400. if (obj instanceof EntityManager) {
  6401. eq.entityManager = obj;
  6402. } else if (MergeStrategy.contains(obj) || FetchStrategy.contains(obj)) {
  6403. var queryOptions = this.queryOptions || QueryOptions.defaultInstance;
  6404. eq.queryOptions = queryOptions.using(obj);
  6405. } else if (obj instanceof DataService) {
  6406. eq.dataService = obj;
  6407. } else if (obj instanceof JsonResultsAdapter) {
  6408. eq.jsonResultsAdapter = obj;
  6409. } else {
  6410. throw new Error("EntityQuery.using parameter must be either an EntityManager, a Query Strategy, a FetchStrategy, a DataService or a JsonResultsAdapter");
  6411. }
  6412. return eq;
  6413. };
  6414. /**
  6415. Executes this query. This method requires that an EntityManager has been previously specified via the "using" method.
  6416. @example
  6417. This method can be called using a 'promises' syntax ( recommended)
  6418. @example
  6419. var em = new EntityManager(serviceName);
  6420. var query = new EntityQuery("Orders").using(em);
  6421. query.execute()
  6422. .then( function(data) {
  6423. ... query results processed here
  6424. }).fail( function(err) {
  6425. ... query failure processed here
  6426. });
  6427. or with callbacks
  6428. @example
  6429. var em = new EntityManager(serviceName);
  6430. var query = new EntityQuery("Orders").using(em);
  6431. query.execute(
  6432. function(data) {
  6433. var orders = data.results;
  6434. ... query results processed here
  6435. },
  6436. function(err) {
  6437. ... query failure processed here
  6438. });
  6439. Either way this method is the same as calling the EntityManager 'execute' method.
  6440. @example
  6441. var em = new EntityManager(serviceName);
  6442. var query = new EntityQuery("Orders");
  6443. em.executeQuery(query)
  6444. .then( function(data) {
  6445. var orders = data.results;
  6446. ... query results processed here
  6447. }).fail( function(err) {
  6448. ... query failure processed here
  6449. });
  6450. @method execute
  6451. @async
  6452. @param callback {Function} Function called on success.
  6453. successFunction([data])
  6454. @param [callback.data] {Object}
  6455. @param callback.data.results {Array of Entity}
  6456. @param callback.data.query {EntityQuery} The original query
  6457. @param callback.data.XHR {XMLHttpRequest} The raw XMLHttpRequest returned from the server.
  6458. @param callback.data.inlineCount {Integer} Only available if 'inlineCount(true)' was applied to the query. Returns the count of
  6459. items that would have been returned by the query before applying any skip or take operators, but after any filter/where predicates
  6460. would have been applied.
  6461. @param errorCallback {Function} Function called on failure.
  6462. failureFunction([error])
  6463. @param [errorCallback.error] {Error} Any error that occured wrapped into an Error object.
  6464. @param [errorCallback.error.query] The query that caused the error.
  6465. @param [errorCallback.error.XHR] {XMLHttpRequest} The raw XMLHttpRequest returned from the server.
  6466. @return {Promise}
  6467. **/
  6468. proto.execute = function (callback, errorCallback) {
  6469. if (!this.entityManager) {
  6470. throw new Error("An EntityQuery must have its EntityManager property set before calling 'execute'");
  6471. }
  6472. return this.entityManager.executeQuery(this, callback, errorCallback);
  6473. };
  6474. /**
  6475. Executes this query against the local cache. This method requires that an EntityManager have been previously specified via the "using" method.
  6476. @example
  6477. // assume em is an entityManager already filled with order entities;
  6478. var query = new EntityQuery("Orders").using(em);
  6479. var orders = query.executeLocally();
  6480. Note that calling this method is the same as calling {{#crossLink "EntityManager/executeQueryLocally"}}{{/crossLink}}.
  6481. @method executeLocally
  6482. **/
  6483. proto.executeLocally = function () {
  6484. if (!this.entityManager) {
  6485. throw new Error("An EntityQuery must have its EntityManager property set before calling 'executeLocally'");
  6486. }
  6487. return this.entityManager.executeQueryLocally(this);
  6488. };
  6489. /**
  6490. Static method tht creates an EntityQuery that will allow 'requerying' an entity or a collection of entities by primary key. This can be useful
  6491. to force a requery of selected entities, or to restrict an existing collection of entities according to some filter.
  6492. @example
  6493. // assuming 'customers' is an array of 'Customer' entities retrieved earlier.
  6494. var customersQuery = EntityQuery.fromEntities(customers);
  6495. The resulting query can, of course, be extended
  6496. @example
  6497. // assuming 'customers' is an array of 'Customer' entities retrieved earlier.
  6498. var customersQuery = EntityQuery.fromEntities(customers)
  6499. .where("Region", FilterQueryOp.NotEquals, null);
  6500. Single entities can requeried as well.
  6501. @example
  6502. // assuming 'customer' is a 'Customer' entity retrieved earlier.
  6503. var customerQuery = EntityQuery.fromEntities(customer);
  6504. will create a query that will return an array containing a single customer entity.
  6505. @method fromEntities
  6506. @static
  6507. @param entities {Entity|Array of Entity} The entities for which we want to create an EntityQuery.
  6508. @return {EntityQuery}
  6509. @chainable
  6510. **/
  6511. ctor.fromEntities = function (entities) {
  6512. assertParam(entities, "entities").isEntity().or().isNonEmptyArray().isEntity().check();
  6513. if (!Array.isArray(entities)) {
  6514. entities = Array.prototype.slice.call(arguments);
  6515. }
  6516. var firstEntity = entities[0];
  6517. var q = new EntityQuery(firstEntity.entityType.defaultResourceName);
  6518. var preds = entities.map(function (entity) {
  6519. return buildPredicate(entity);
  6520. });
  6521. var pred = Predicate.or(preds);
  6522. q = q.where(pred);
  6523. var em = firstEntity.entityAspect.entityManager;
  6524. if (em) {
  6525. q = q.using(em);
  6526. }
  6527. return q;
  6528. };
  6529. /**
  6530. Creates an EntityQuery for the specified {{#crossLink "EntityKey"}}{{/crossLink}}.
  6531. @example
  6532. var empType = metadataStore.getEntityType("Employee");
  6533. var entityKey = new EntityKey(empType, 1);
  6534. var query = EntityQuery.fromEntityKey(entityKey);
  6535. or
  6536. @example
  6537. // 'employee' is a previously queried employee
  6538. var entityKey = employee.entityAspect.getKey();
  6539. var query = EntityQuery.fromEntityKey(entityKey);
  6540. @method fromEntityKey
  6541. @static
  6542. @param entityKey {EntityKey} The {{#crossLink "EntityKey"}}{{/crossLink}} for which a query will be created.
  6543. @return {EntityQuery}
  6544. @chainable
  6545. **/
  6546. ctor.fromEntityKey = function (entityKey) {
  6547. assertParam(entityKey, "entityKey").isInstanceOf(EntityKey).check();
  6548. var q = new EntityQuery(entityKey.entityType.defaultResourceName);
  6549. var pred = buildKeyPredicate(entityKey);
  6550. q = q.where(pred);
  6551. return q;
  6552. };
  6553. /**
  6554. Creates an EntityQuery for the specified entity and {{#crossLink "NavigationProperty"}}{{/crossLink}}.
  6555. @example
  6556. // 'employee' is a previously queried employee
  6557. var ordersNavProp = employee.entityType.getProperty("Orders");
  6558. var query = EntityQuery.fromEntityNavigation(employee, ordersNavProp);
  6559. will return a query for the "Orders" of the specified 'employee'.
  6560. @method fromEntityNavigation
  6561. @static
  6562. @param entity {Entity} The Entity whose navigation property will be queried.
  6563. @param navigationProperty {NavigationProperty} The {{#crossLink "NavigationProperty"}}{{/crossLink}} to be queried.
  6564. @return {EntityQuery}
  6565. @chainable
  6566. **/
  6567. ctor.fromEntityNavigation = function (entity, navigationProperty) {
  6568. assertParam(entity, "entity").isEntity().check();
  6569. assertParam(navigationProperty, "navigationProperty").isInstanceOf(NavigationProperty).check();
  6570. var navProperty = entity.entityType._checkNavProperty(navigationProperty);
  6571. var q = new EntityQuery(navProperty.entityType.defaultResourceName);
  6572. var pred = buildNavigationPredicate(entity, navProperty);
  6573. q = q.where(pred);
  6574. var em = entity.entityAspect.entityManager;
  6575. if (em) {
  6576. q = q.using(em);
  6577. }
  6578. return q;
  6579. };
  6580. // protected methods
  6581. proto._getFromEntityType = function (metadataStore, throwErrorIfNotFound) {
  6582. // Uncomment next two lines if we make this method public.
  6583. // assertParam(metadataStore, "metadataStore").isInstanceOf(MetadataStore).check();
  6584. // assertParam(throwErrorIfNotFound, "throwErrorIfNotFound").isBoolean().isOptional().check();
  6585. var entityType = this.entityType;
  6586. if (!entityType) {
  6587. var resourceName = this.resourceName;
  6588. if (!resourceName) {
  6589. throw new Error("There is no resourceName for this query");
  6590. }
  6591. if (metadataStore.isEmpty()) {
  6592. if (throwErrorIfNotFound) {
  6593. throw new Error("There is no metadata available for this query");
  6594. } else {
  6595. return null;
  6596. }
  6597. }
  6598. var entityTypeName = metadataStore.getEntityTypeNameForResourceName(resourceName);
  6599. if (!entityTypeName) {
  6600. if (throwErrorIfNotFound) {
  6601. throw new Error("Cannot find resourceName of: " + resourceName);
  6602. } else {
  6603. return null;
  6604. }
  6605. }
  6606. entityType = metadataStore.getEntityType(entityTypeName);
  6607. if (!entityType) {
  6608. if (throwErrorIfNotFound) {
  6609. throw new Error("Cannot find an entityType for an entityTypeName of: " + entityTypeName);
  6610. } else {
  6611. return null;
  6612. }
  6613. }
  6614. this.entityType = entityType;
  6615. }
  6616. return entityType;
  6617. };
  6618. proto._getToEntityType = function (metadataStore) {
  6619. if (this.toEntityType instanceof EntityType) {
  6620. return this.toEntityType;
  6621. } else if (this.toEntityType) {
  6622. // toEntityType is a string
  6623. this.toEntityType = metadataStore.getEntityType(this.toEntityType, false);
  6624. return this.toEntityType;
  6625. } else {
  6626. // resolve it, if possible, via the resourceName
  6627. // do not cache this value in this case
  6628. // cannot determine the toEntityType if a selectClause is present.
  6629. return (!this.selectClause) && this._getFromEntityType(metadataStore, false);
  6630. }
  6631. };
  6632. proto._clone = function () {
  6633. var copy = new EntityQuery();
  6634. copy.resourceName = this.resourceName;
  6635. copy.entityType = this.entityType;
  6636. copy.wherePredicate = this.wherePredicate;
  6637. copy.orderByClause = this.orderByClause;
  6638. copy.selectClause = this.selectClause;
  6639. copy.skipCount = this.skipCount;
  6640. copy.takeCount = this.takeCount;
  6641. copy.expandClause = this.expandClause;
  6642. copy.inlineCountEnabled = this.inlineCountEnabled;
  6643. copy.parameters = __extend({}, this.parameters);
  6644. // default is to get queryOptions from the entityManager.
  6645. copy.queryOptions = this.queryOptions;
  6646. copy.entityManager = this.entityManager;
  6647. return copy;
  6648. };
  6649. proto._toUri = function (metadataStore) {
  6650. // force entityType validation;
  6651. var entityType = this._getFromEntityType(metadataStore, false);
  6652. if (!entityType) {
  6653. entityType = new EntityType(metadataStore);
  6654. }
  6655. var eq = this;
  6656. var queryOptions = {};
  6657. queryOptions["$filter"] = toFilterString();
  6658. queryOptions["$orderby"] = toOrderByString();
  6659. queryOptions["$skip"] = toSkipString();
  6660. queryOptions["$top"] = toTopString();
  6661. queryOptions["$expand"] = toExpandString();
  6662. queryOptions["$select"] = toSelectString();
  6663. queryOptions["$inlinecount"] = toInlineCountString();
  6664. queryOptions = __extend(queryOptions, this.parameters);
  6665. var qoText = toQueryOptionsString(queryOptions);
  6666. return this.resourceName + qoText;
  6667. // private methods to this func.
  6668. function toFilterString() {
  6669. var clause = eq.wherePredicate;
  6670. if (!clause) return "";
  6671. if (eq.entityType) {
  6672. clause.validate(eq.entityType);
  6673. }
  6674. return clause.toOdataFragment(entityType);
  6675. }
  6676. function toInlineCountString() {
  6677. if (!eq.inlineCountEnabled) return "";
  6678. return eq.inlineCountEnabled ? "allpages" : "none";
  6679. }
  6680. function toOrderByString() {
  6681. var clause = eq.orderByClause;
  6682. if (!clause) return "";
  6683. if (eq.entityType) {
  6684. clause.validate(eq.entityType);
  6685. }
  6686. return clause.toOdataFragment(entityType);
  6687. }
  6688. function toSelectString() {
  6689. var clause = eq.selectClause;
  6690. if (!clause) return "";
  6691. if (eq.entityType) {
  6692. clause.validate(eq.entityType);
  6693. }
  6694. return clause.toOdataFragment(entityType);
  6695. }
  6696. function toExpandString() {
  6697. var clause = eq.expandClause;
  6698. if (!clause) return "";
  6699. return clause.toOdataFragment(entityType);
  6700. }
  6701. function toSkipString() {
  6702. var count = eq.skipCount;
  6703. if (!count) return "";
  6704. return count.toString();
  6705. }
  6706. function toTopString() {
  6707. var count = eq.takeCount;
  6708. if (!count) return "";
  6709. return count.toString();
  6710. }
  6711. function toQueryOptionsString(queryOptions) {
  6712. var qoStrings = [];
  6713. for (var qoName in queryOptions) {
  6714. var qoValue = queryOptions[qoName];
  6715. if (qoValue) {
  6716. qoStrings.push(qoName + "=" + encodeURIComponent(qoValue));
  6717. }
  6718. }
  6719. if (qoStrings.length > 0) {
  6720. return "?" + qoStrings.join("&");
  6721. } else {
  6722. return "";
  6723. }
  6724. }
  6725. };
  6726. proto._toFilterFunction = function (entityType) {
  6727. var wherePredicate = this.wherePredicate;
  6728. if (!wherePredicate) return null;
  6729. // may throw an exception
  6730. wherePredicate.validate(entityType);
  6731. return wherePredicate.toFunction(entityType);
  6732. };
  6733. proto._toOrderByComparer = function (entityType) {
  6734. var orderByClause = this.orderByClause;
  6735. if (!orderByClause) return null;
  6736. // may throw an exception
  6737. orderByClause.validate(entityType);
  6738. return orderByClause.getComparer();
  6739. };
  6740. // private functions
  6741. function normalizeResourceName(resourceName) {
  6742. return resourceName;
  6743. // if (resourceName) {
  6744. // return resourceName.toLowerCase();
  6745. // } else {
  6746. // return undefined;
  6747. // }
  6748. }
  6749. function normalizePropertyPaths(propertyPaths) {
  6750. assertParam(propertyPaths, "propertyPaths").isOptional().isString().or().isArray().isString().check();
  6751. if (typeof propertyPaths === 'string') {
  6752. propertyPaths = propertyPaths.split(",");
  6753. }
  6754. propertyPaths = propertyPaths.map(function (pp) {
  6755. return pp.trim();
  6756. });
  6757. return propertyPaths;
  6758. }
  6759. function buildPredicate(entity) {
  6760. var entityType = entity.entityType;
  6761. var predParts = entityType.keyProperties.map(function (kp) {
  6762. return Predicate.create(kp.name, FilterQueryOp.Equals, entity.getProperty(kp.name));
  6763. });
  6764. var pred = Predicate.and(predParts);
  6765. return pred;
  6766. }
  6767. // propertyPaths: can pass in create("A.X,B") or create("A.X desc, B") or create("A.X desc,B", true])
  6768. // isDesc parameter trumps isDesc in propertyName.
  6769. function orderByCore(that, propertyPaths, isDesc) {
  6770. var newClause;
  6771. var eq = that._clone();
  6772. if (!propertyPaths) {
  6773. eq.orderByClause = null;
  6774. return eq;
  6775. }
  6776. newClause = OrderByClause.create(propertyPaths, isDesc);
  6777. if (eq.orderByClause) {
  6778. eq.orderByClause.addClause(newClause);
  6779. } else {
  6780. eq.orderByClause = newClause;
  6781. }
  6782. return eq;
  6783. }
  6784. function selectCore(that, propertyPaths) {
  6785. var eq = that._clone();
  6786. if (!propertyPaths) {
  6787. eq.selectClause = null;
  6788. return eq;
  6789. }
  6790. eq.selectClause = new SelectClause(propertyPaths);
  6791. return eq;
  6792. }
  6793. function expandCore(that, propertyPaths) {
  6794. var eq = that._clone();
  6795. if (!propertyPaths) {
  6796. eq.expandClause = null;
  6797. return eq;
  6798. }
  6799. eq.expandClause = new ExpandClause(propertyPaths);
  6800. return eq;
  6801. }
  6802. function withParametersCore(that, parameters) {
  6803. var eq = that._clone();
  6804. eq.parameters = parameters;
  6805. return eq;
  6806. }
  6807. function buildKeyPredicate(entityKey) {
  6808. var keyProps = entityKey.entityType.keyProperties;
  6809. var preds = __arrayZip(keyProps, entityKey.values, function (kp, v) {
  6810. return Predicate.create(kp.name, FilterQueryOp.Equals, v);
  6811. });
  6812. var pred = Predicate.and(preds);
  6813. return pred;
  6814. }
  6815. function buildNavigationPredicate(entity, navigationProperty) {
  6816. if (navigationProperty.isScalar) {
  6817. if (navigationProperty.foreignKeyNames.length === 0) return null;
  6818. var relatedKeyValues = navigationProperty.foreignKeyNames.map(function (fkName) {
  6819. return entity.getProperty(fkName);
  6820. });
  6821. var entityKey = new EntityKey(navigationProperty.entityType, relatedKeyValues);
  6822. return buildKeyPredicate(entityKey);
  6823. } else {
  6824. var inverseNp = navigationProperty.inverse;
  6825. if (!inverseNp) return null;
  6826. var foreignKeyNames = inverseNp.foreignKeyNames;
  6827. if (foreignKeyNames.length === 0) return null;
  6828. var keyValues = entity.entityAspect.getKey().values;
  6829. var predParts = __arrayZip(foreignKeyNames, keyValues, function (fkName, kv) {
  6830. return Predicate.create(fkName, FilterQueryOp.Equals, kv);
  6831. });
  6832. var pred = Predicate.and(predParts);
  6833. return pred;
  6834. }
  6835. }
  6836. return ctor;
  6837. })();
  6838. var QueryFuncs = (function() {
  6839. var obj = {
  6840. toupper: { fn: function (source) { return source.toUpperCase(); }, dataType: DataType.String },
  6841. tolower: { fn: function (source) { return source.toLowerCase(); }, dataType: DataType.String },
  6842. substring: { fn: function (source, pos, length) { return source.substring(pos, length); }, dataType: DataType.String },
  6843. substringof: { fn: function (find, source) { return source.indexOf(find) >= 0;}, dataType: DataType.Boolean },
  6844. length: { fn: function(source) { return source.length; }, dataType: DataType.Int32 },
  6845. trim: { fn: function (source) { return source.trim(); }, dataType: DataType.String },
  6846. concat: { fn: function (s1, s2) { return s1.concat(s2); }, dataType: DataType.String },
  6847. replace: { fn: function (source, find, replace) { return source.replace(find, replace); }, dataType: DataType.String },
  6848. startswith: { fn: function (source, find) { return __stringStartsWith(source, find); }, dataType: DataType.Boolean },
  6849. endswith: { fn: function (source, find) { return __stringEndsWith(source, find); }, dataType: DataType.Boolean },
  6850. indexof: { fn: function (source, find) { return source.indexOf(find); }, dataType: DataType.Int32 },
  6851. round: { fn: function (source) { return Math.round(source); }, dataType: DataType.Int32 },
  6852. ceiling: { fn: function (source) { return Math.ceil(source); }, dataType: DataType.Int32 },
  6853. floor: { fn: function (source) { return Math.floor(source); }, dataType: DataType.Int32 },
  6854. second: { fn: function (source) { return source.second; }, dataType: DataType.Int32 },
  6855. minute: { fn: function (source) { return source.minute; }, dataType: DataType.Int32 },
  6856. day: { fn: function (source) { return source.day; }, dataType: DataType.Int32 },
  6857. month: { fn: function (source) { return source.month; }, dataType: DataType.Int32 },
  6858. year: { fn: function (source) { return source.year; }, dataType: DataType.Int32 }
  6859. };
  6860. return obj;
  6861. })();
  6862. var FnNode = (function() {
  6863. // valid property name identifier
  6864. var RX_IDENTIFIER = /^[a-z_][\w.$]*$/i ;
  6865. // comma delimited expressions ignoring commas inside of quotes.
  6866. var RX_COMMA_DELIM1 = /('[^']*'|[^,]+)/g ;
  6867. var RX_COMMA_DELIM2 = /("[^"]*"|[^,]+)/g ;
  6868. var ctor = function (source, tokens, entityType) {
  6869. var parts = source.split(":");
  6870. if (entityType) {
  6871. this.isValidated = true;
  6872. }
  6873. if (parts.length == 1) {
  6874. var value = parts[0].trim();
  6875. this.value = value;
  6876. // value is either a string, a quoted string, a number, a bool value, or a date
  6877. // if a string ( not a quoted string) then this represents a property name.
  6878. var firstChar = value.substr(0,1);
  6879. var quoted = firstChar == "'" || firstChar == '"';
  6880. if (quoted) {
  6881. var unquoted = value.substr(1, value.length - 2);
  6882. this.fn = function (entity) { return unquoted; };
  6883. this.dataType = DataType.String;
  6884. } else {
  6885. var mayBeIdentifier = RX_IDENTIFIER.test(value);
  6886. if (mayBeIdentifier) {
  6887. if (entityType) {
  6888. if (entityType.getProperty(value, false) == null) {
  6889. // not a real FnNode;
  6890. this.isValidated = false;
  6891. return;
  6892. }
  6893. }
  6894. this.propertyPath = value;
  6895. this.fn = createPropFunction(value);
  6896. } else {
  6897. if (entityType) {
  6898. this.isValidated = false;
  6899. return;
  6900. }
  6901. this.fn = function (entity) { return value; };
  6902. this.dataType = DataType.fromValue(value);
  6903. }
  6904. }
  6905. } else {
  6906. try {
  6907. this.fnName = parts[0].trim().toLowerCase();
  6908. var qf = QueryFuncs[this.fnName];
  6909. this.localFn = qf.fn;
  6910. this.dataType = qf.dataType;
  6911. var that = this;
  6912. this.fn = function(entity) {
  6913. var resolvedNodes = that.fnNodes.map(function(fnNode) {
  6914. var argVal = fnNode.fn(entity);
  6915. return argVal;
  6916. });
  6917. var val = that.localFn.apply(null, resolvedNodes);
  6918. return val;
  6919. };
  6920. var argSource = tokens[parts[1]].trim();
  6921. if (argSource.substr(0, 1) == "(") {
  6922. argSource = argSource.substr(1, argSource.length - 2);
  6923. }
  6924. var commaMatchStr = source.indexOf("'") >= 0 ? RX_COMMA_DELIM1 : RX_COMMA_DELIM2;
  6925. var args = argSource.match(commaMatchStr);
  6926. this.fnNodes = args.map(function(a) {
  6927. return new FnNode(a, tokens);
  6928. });
  6929. } catch (e) {
  6930. this.isValidated = false;
  6931. }
  6932. }
  6933. };
  6934. var proto = ctor.prototype;
  6935. ctor.create = function (source, entityType) {
  6936. if (typeof source !== 'string') {
  6937. return null;
  6938. }
  6939. var regex = /\([^()]*\)/ ;
  6940. var m;
  6941. var tokens = [];
  6942. var i = 0;
  6943. while (m = regex.exec(source)) {
  6944. var token = m[0];
  6945. tokens.push(token);
  6946. var repl = ":" + i++;
  6947. source = source.replace(token, repl);
  6948. }
  6949. var node = new FnNode(source, tokens, entityType);
  6950. // isValidated may be undefined
  6951. return node.isValidated === false ? null : node;
  6952. };
  6953. proto.toString = function() {
  6954. if (this.fnName) {
  6955. var args = this.fnNodes.map(function(fnNode) {
  6956. return fnNode.toString();
  6957. });
  6958. var uri = this.fnName + "(" + args.join(",") + ")";
  6959. return uri;
  6960. } else {
  6961. return this.value;
  6962. }
  6963. };
  6964. proto.updateWithEntityType = function(entityType) {
  6965. if (this.propertyPath) {
  6966. if (entityType.isAnonymous) return;
  6967. var prop = entityType.getProperty(this.propertyPath);
  6968. if (!prop) {
  6969. var msg = __formatString("Unable to resolve propertyPath. EntityType: '%1' PropertyPath: '%2'", entityType.name, this.propertyPath);
  6970. throw new Error(msg);
  6971. }
  6972. this.dataType = prop.dataType;
  6973. }
  6974. };
  6975. proto.toOdataFragment = function (entityType) {
  6976. this.updateWithEntityType(entityType);
  6977. if (this.fnName) {
  6978. var args = this.fnNodes.map(function(fnNode) {
  6979. return fnNode.toOdataFragment(entityType);
  6980. });
  6981. var uri = this.fnName + "(" + args.join(",") + ")";
  6982. return uri;
  6983. } else {
  6984. var firstChar = this.value.substr(0, 1);
  6985. if (firstChar === "'" || firstChar === '"') {
  6986. return this.value;
  6987. } else if (this.value == this.propertyPath) {
  6988. return entityType._clientPropertyPathToServer(this.propertyPath);
  6989. } else {
  6990. return this.value;
  6991. }
  6992. }
  6993. };
  6994. proto.validate = function(entityType) {
  6995. // will throw if not found;
  6996. if (this.isValidated !== undefined) return;
  6997. this.isValidated = true;
  6998. if (this.propertyPath) {
  6999. var prop = entityType.getProperty(this.propertyPath, true);
  7000. if (prop.isDataProperty) {
  7001. this.dataType = prop.dataType;
  7002. } else {
  7003. this.dataType = prop.entityType;
  7004. }
  7005. } else if (this.fnNodes) {
  7006. this.fnNodes.forEach(function(node) {
  7007. node.validate(entityType);
  7008. });
  7009. }
  7010. };
  7011. function createPropFunction(propertyPath) {
  7012. var properties = propertyPath.split('.');
  7013. if (properties.length === 1) {
  7014. return function (entity) {
  7015. return entity.getProperty(propertyPath);
  7016. };
  7017. } else {
  7018. return function (entity) {
  7019. return getPropertyPathValue(entity, properties);
  7020. };
  7021. }
  7022. }
  7023. return ctor;
  7024. })();
  7025. var FilterQueryOp = function () {
  7026. /**
  7027. FilterQueryOp is an 'Enum' containing all of the valid {{#crossLink "Predicate"}}{{/crossLink}}
  7028. filter operators for an {{#crossLink "EntityQuery"}}{{/crossLink}}.
  7029. @class FilterQueryOp
  7030. @static
  7031. **/
  7032. var aEnum = new Enum("FilterQueryOp");
  7033. /**
  7034. Aliases: "eq", "=="
  7035. @property Equals {FilterQueryOp}
  7036. @final
  7037. @static
  7038. **/
  7039. aEnum.Equals = aEnum.addSymbol({ operator: "eq", aliases: ["=="] });
  7040. /**
  7041. Aliases: "ne", "!="
  7042. @property NotEquals {FilterQueryOp}
  7043. @final
  7044. @static
  7045. **/
  7046. aEnum.NotEquals = aEnum.addSymbol({ operator: "ne", aliases: ["!="] });
  7047. /**
  7048. Aliases: "gt", ">"
  7049. @property GreaterThan {FilterQueryOp}
  7050. @final
  7051. @static
  7052. **/
  7053. aEnum.GreaterThan = aEnum.addSymbol({ operator: "gt", aliases: [">"] });
  7054. /**
  7055. Aliases: "lt", "<"
  7056. @property LessThan {FilterQueryOp}
  7057. @final
  7058. @static
  7059. **/
  7060. aEnum.LessThan = aEnum.addSymbol({ operator: "lt", aliases: ["<"] });
  7061. /**
  7062. Aliases: "ge", ">="
  7063. @property GreaterThanOrEqual {FilterQueryOp}
  7064. @final
  7065. @static
  7066. **/
  7067. aEnum.GreaterThanOrEqual = aEnum.addSymbol({ operator: "ge", aliases: [">="] });
  7068. /**
  7069. Aliases: "le", "<="
  7070. @property LessThanOrEqual {FilterQueryOp}
  7071. @final
  7072. @static
  7073. **/
  7074. aEnum.LessThanOrEqual = aEnum.addSymbol({ operator: "le", aliases: ["<="] });
  7075. /**
  7076. String operation: Is a string a substring of another string.
  7077. Aliases: "substringof"
  7078. @property Contains {FilterQueryOp}
  7079. @final
  7080. @static
  7081. **/
  7082. aEnum.Contains = aEnum.addSymbol({ operator: "substringof", isFunction: true });
  7083. /**
  7084. @property StartsWith {FilterQueryOp}
  7085. @final
  7086. @static
  7087. **/
  7088. aEnum.StartsWith = aEnum.addSymbol({ operator: "startswith", isFunction: true });
  7089. /**
  7090. @property EndsWith {FilterQueryOp}
  7091. @final
  7092. @static
  7093. **/
  7094. aEnum.EndsWith = aEnum.addSymbol({ operator: "endswith", isFunction: true });
  7095. aEnum.seal();
  7096. aEnum._map = function () {
  7097. var map = {};
  7098. aEnum.getSymbols().forEach(function (s) {
  7099. map[s.name.toLowerCase()] = s;
  7100. map[s.operator.toLowerCase()] = s;
  7101. if (s.aliases) {
  7102. s.aliases.forEach(function (alias) {
  7103. map[alias.toLowerCase()] = s;
  7104. });
  7105. }
  7106. });
  7107. return map;
  7108. } ();
  7109. aEnum.from = function (op) {
  7110. if (aEnum.contains(op)) {
  7111. return op;
  7112. } else {
  7113. return aEnum._map[op.toLowerCase()];
  7114. }
  7115. };
  7116. return aEnum;
  7117. } ();
  7118. var BooleanQueryOp = function () {
  7119. var aEnum = new Enum("BooleanQueryOp");
  7120. aEnum.And = aEnum.addSymbol({ operator: "and", aliases: ["&&"] });
  7121. aEnum.Or = aEnum.addSymbol({ operator: "or", aliases: ["||"] });
  7122. aEnum.Not = aEnum.addSymbol({ operator: "not", aliases: ["~", "!"] });
  7123. aEnum.seal();
  7124. aEnum._map = function () {
  7125. var map = {};
  7126. aEnum.getSymbols().forEach(function (s) {
  7127. map[s.name.toLowerCase()] = s;
  7128. map[s.operator.toLowerCase()] = s;
  7129. if (s.aliases) {
  7130. s.aliases.forEach(function (alias) {
  7131. map[alias.toLowerCase()] = s;
  7132. });
  7133. }
  7134. });
  7135. return map;
  7136. } ();
  7137. aEnum.from = function (op) {
  7138. if (aEnum.contains(op)) {
  7139. return op;
  7140. } else {
  7141. return aEnum._map[op.toLowerCase()];
  7142. }
  7143. };
  7144. return aEnum;
  7145. } ();
  7146. var Predicate = (function () {
  7147. /**
  7148. Used to define a 'where' predicate for an EntityQuery. Predicates are immutable, which means that any
  7149. method that would modify a Predicate actually returns a new Predicate.
  7150. @class Predicate
  7151. **/
  7152. /**
  7153. Predicate constructor
  7154. @example
  7155. var p1 = new Predicate("CompanyName", "StartsWith", "B");
  7156. var query = new EntityQuery("Customers").where(p1);
  7157. or
  7158. @example
  7159. var p2 = new Predicate("Region", FilterQueryOp.Equals, null);
  7160. var query = new EntityQuery("Customers").where(p2);
  7161. @method <ctor> Predicate
  7162. @param property {String} A property name, a nested property name or an expression involving a property name.
  7163. @param operator {FilterQueryOp|String}
  7164. @param value {Object} - This will be treated as either a property expression or a literal depending on context. In general,
  7165. if the value can be interpreted as a property expression it will be, otherwise it will be treated as a literal.
  7166. In most cases this works well, but you can also force the interpretation by setting the next parameter 'valueIsLiteral' to true.
  7167. @param [valueIsLiteral] {Boolean} - Used to force the 'value' parameter to be treated as a literal - otherwise this will be inferred based on the context.
  7168. **/
  7169. var ctor = function (propertyOrExpr, operator, value, valueIsLiteral) {
  7170. if (arguments[0].prototype === true) {
  7171. // used to construct prototype
  7172. return this;
  7173. }
  7174. return new SimplePredicate(propertyOrExpr, operator, value, valueIsLiteral);
  7175. };
  7176. var proto = ctor.prototype;
  7177. /**
  7178. Returns whether an object is a Predicate
  7179. @example
  7180. var p1 = new Predicate("CompanyName", "StartsWith", "B");
  7181. if (Predicate.isPredicate(p1)) {
  7182. // do something
  7183. }
  7184. @method isPredicate
  7185. @param o {Object}
  7186. @static
  7187. **/
  7188. ctor.isPredicate = function (o) {
  7189. return o instanceof Predicate;
  7190. };
  7191. /**
  7192. Creates a new 'simple' Predicate. Note that this method can also take its parameters as an array.
  7193. @example
  7194. var p1 = Predicate.create("Freight", "gt", 100);
  7195. or parameters can be passed as an array.
  7196. @example
  7197. var predArgs = ["Freight", "gt", 100];
  7198. var p1 = Predicate.create(predArgs);
  7199. both of these are the same as
  7200. @example
  7201. var p1 = new Predicate("Freight", "gt", 100);
  7202. @method create
  7203. @static
  7204. @param property {String} A property name, a nested property name or an expression involving a property name.
  7205. @param operator {FilterQueryOp|String}
  7206. @param value {Object} - This will be treated as either a property expression or a literal depending on context. In general,
  7207. if the value can be interpreted as a property expression it will be, otherwise it will be treated as a literal.
  7208. In most cases this works well, but you can also force the interpretation by setting the next parameter 'valueIsLiteral' to true.
  7209. @param [valueIsLiteral] {Boolean} - Used to force the 'value' parameter to be treated as a literal - otherwise this will be inferred based on the context.
  7210. **/
  7211. ctor.create = function (property, operator, value, valueIsLiteral) {
  7212. if (Array.isArray(property)) {
  7213. valueIsLiteral = (property.length === 4) ? property[3] : false;
  7214. return new SimplePredicate(property[0], property[1], property[2], valueIsLiteral);
  7215. } else {
  7216. return new SimplePredicate(property, operator, value, valueIsLiteral);
  7217. }
  7218. };
  7219. /**
  7220. Creates a 'composite' Predicate by 'and'ing a set of specified Predicates together.
  7221. @example
  7222. var dt = new Date(88, 9, 12);
  7223. var p1 = Predicate.create("OrderDate", "ne", dt);
  7224. var p2 = Predicate.create("ShipCity", "startsWith", "C");
  7225. var p3 = Predicate.create("Freight", ">", 100);
  7226. var newPred = Predicate.and(p1, p2, p3);
  7227. or
  7228. @example
  7229. var preds = [p1, p2, p3];
  7230. var newPred = Predicate.and(preds);
  7231. @method and
  7232. @param predicates* {multiple Predicates|Array of Predicate}
  7233. @static
  7234. **/
  7235. ctor.and = function (predicates) {
  7236. predicates = argsToPredicates(arguments);
  7237. if (predicates.length === 1) {
  7238. return predicates[0];
  7239. } else {
  7240. return new CompositePredicate("and", predicates);
  7241. }
  7242. };
  7243. /**
  7244. Creates a 'composite' Predicate by 'or'ing a set of specified Predicates together.
  7245. @example
  7246. var dt = new Date(88, 9, 12);
  7247. var p1 = Predicate.create("OrderDate", "ne", dt);
  7248. var p2 = Predicate.create("ShipCity", "startsWith", "C");
  7249. var p3 = Predicate.create("Freight", ">", 100);
  7250. var newPred = Predicate.or(p1, p2, p3);
  7251. or
  7252. @example
  7253. var preds = [p1, p2, p3];
  7254. var newPred = Predicate.or(preds);
  7255. @method or
  7256. @param predicates* {multiple Predicates|Array of Predicate}
  7257. @static
  7258. **/
  7259. ctor.or = function (predicates) {
  7260. predicates = argsToPredicates(arguments);
  7261. if (predicates.length === 1) {
  7262. return predicates[0];
  7263. } else {
  7264. return new CompositePredicate("or", predicates);
  7265. }
  7266. };
  7267. /**
  7268. Creates a 'composite' Predicate by 'negating' a specified predicate.
  7269. @example
  7270. var p1 = Predicate.create("Freight", "gt", 100);
  7271. var not_p1 = Predicate.not(p1);
  7272. This can also be accomplished using the 'instance' version of the 'not' method
  7273. @example
  7274. var not_p1 = p1.not();
  7275. Both of which would be the same as
  7276. @example
  7277. var not_p1 = Predicate.create("Freight", "le", 100);
  7278. @method not
  7279. @param predicate {Predicate}
  7280. @static
  7281. **/
  7282. ctor.not = function (predicate) {
  7283. return new CompositePredicate("not", [predicate]);
  7284. };
  7285. /**
  7286. 'And's this Predicate with one or more other Predicates and returns a new 'composite' Predicate
  7287. @example
  7288. var dt = new Date(88, 9, 12);
  7289. var p1 = Predicate.create("OrderDate", "ne", dt);
  7290. var p2 = Predicate.create("ShipCity", "startsWith", "C");
  7291. var p3 = Predicate.create("Freight", ">", 100);
  7292. var newPred = p1.and(p2, p3);
  7293. or
  7294. @example
  7295. var preds = [p2, p3];
  7296. var newPred = p1.and(preds);
  7297. The 'and' method is also used to write "fluent" expressions
  7298. @example
  7299. var p4 = Predicate.create("ShipCity", "startswith", "F")
  7300. .and("Size", "gt", 2000);
  7301. @method and
  7302. @param predicates* {multiple Predicates|Array of Predicate}
  7303. **/
  7304. proto.and = function (predicates) {
  7305. predicates = argsToPredicates(arguments);
  7306. predicates.unshift(this);
  7307. return ctor.and(predicates);
  7308. };
  7309. /**
  7310. 'Or's this Predicate with one or more other Predicates and returns a new 'composite' Predicate
  7311. @example
  7312. var dt = new Date(88, 9, 12);
  7313. var p1 = Predicate.create("OrderDate", "ne", dt);
  7314. var p2 = Predicate.create("ShipCity", "startsWith", "C");
  7315. var p3 = Predicate.create("Freight", ">", 100);
  7316. var newPred = p1.and(p2, p3);
  7317. or
  7318. @example
  7319. var preds = [p2, p3];
  7320. var newPred = p1.and(preds);
  7321. The 'or' method is also used to write "fluent" expressions
  7322. @example
  7323. var p4 = Predicate.create("ShipCity", "startswith", "F")
  7324. .or("Size", "gt", 2000);
  7325. @method or
  7326. @param predicates* {multiple Predicates|Array of Predicate}
  7327. **/
  7328. proto.or = function (predicates) {
  7329. predicates = argsToPredicates(arguments);
  7330. predicates.unshift(this);
  7331. return ctor.or(predicates);
  7332. };
  7333. /**
  7334. Returns the 'negated' version of this Predicate
  7335. @example
  7336. var p1 = Predicate.create("Freight", "gt", 100);
  7337. var not_p1 = p1.not();
  7338. This can also be accomplished using the 'static' version of the 'not' method
  7339. @example
  7340. var p1 = Predicate.create("Freight", "gt", 100);
  7341. var not_p1 = Predicate.not(p1);
  7342. which would be the same as
  7343. @example
  7344. var not_p1 = Predicate.create("Freight", "le", 100);
  7345. @method not
  7346. **/
  7347. proto.not = function () {
  7348. return new CompositePredicate("not", [this]);
  7349. };
  7350. // methods defined in both subclasses of Predicate
  7351. /**
  7352. Returns the function that will be used to execute this Predicate against the local cache.
  7353. @method toFunction
  7354. @return {Function}
  7355. **/
  7356. /**
  7357. Returns a human readable string for this Predicate.
  7358. @method toString
  7359. @return {String}
  7360. **/
  7361. /**
  7362. Determines whether this Predicate is 'valid' for the specified EntityType; This method will throw an exception
  7363. if invalid.
  7364. @method validate
  7365. @param entityType {EntityType} The entityType to validate against.
  7366. **/
  7367. function argsToPredicates(argsx) {
  7368. if (argsx.length === 1 && Array.isArray(argsx[0])) {
  7369. return argsx[0];
  7370. } else {
  7371. var args = Array.prototype.slice.call(argsx);
  7372. if (Predicate.isPredicate(args[0])) {
  7373. return args;
  7374. } else {
  7375. return [Predicate.create(args)];
  7376. }
  7377. }
  7378. }
  7379. return ctor;
  7380. })();
  7381. // Does not need to be exposed.
  7382. var SimplePredicate = (function () {
  7383. var ctor = function (propertyOrExpr, operator, value, valueIsLiteral) {
  7384. assertParam(propertyOrExpr, "propertyOrExpr").isString().check();
  7385. assertParam(operator, "operator").isEnumOf(FilterQueryOp).or().isString().check();
  7386. assertParam(value, "value").isRequired(true).check();
  7387. assertParam(valueIsLiteral).isOptional().isBoolean().check();
  7388. this._propertyOrExpr = propertyOrExpr;
  7389. this._fnNode1 = FnNode.create(propertyOrExpr, null);
  7390. this._filterQueryOp = FilterQueryOp.from(operator);
  7391. if (!this._filterQueryOp) {
  7392. throw new Error("Unknown query operation: " + operator);
  7393. }
  7394. this._value = value;
  7395. this._valueIsLiteral = valueIsLiteral;
  7396. };
  7397. var proto = new Predicate({ prototype: true });
  7398. ctor.prototype = proto;
  7399. proto.toOdataFragment = function (entityType) {
  7400. var v1Expr = this._fnNode1.toOdataFragment(entityType);
  7401. var v2Expr;
  7402. if (this.fnNode2 === undefined && !this._valueIsLiteral) {
  7403. this.fnNode2 = FnNode.create(this._value, entityType);
  7404. }
  7405. if (this.fnNode2) {
  7406. v2Expr = this.fnNode2.toOdataFragment(entityType);
  7407. } else {
  7408. v2Expr = formatValue(this._value, this._fnNode1.dataType);
  7409. }
  7410. if (this._filterQueryOp.isFunction) {
  7411. if (this._filterQueryOp == FilterQueryOp.Contains) {
  7412. return this._filterQueryOp.operator + "(" + v2Expr + "," + v1Expr + ") eq true";
  7413. } else {
  7414. return this._filterQueryOp.operator + "(" + v1Expr + "," + v2Expr + ") eq true";
  7415. }
  7416. } else {
  7417. return v1Expr + " " + this._filterQueryOp.operator + " " + v2Expr;
  7418. }
  7419. };
  7420. proto.toFunction = function (entityType) {
  7421. var dataType = this._fnNode1.dataType || DataType.fromValue(this._value);
  7422. var predFn = getPredicateFn(entityType, this._filterQueryOp, dataType);
  7423. var v1Fn = this._fnNode1.fn;
  7424. if (this.fnNode2 === undefined && !this._valueIsLiteral) {
  7425. this.fnNode2 = FnNode.create(this._value, entityType);
  7426. }
  7427. if (this.fnNode2) {
  7428. var v2Fn = this.fnNode2.fn;
  7429. return function(entity) {
  7430. return predFn(v1Fn(entity), v2Fn(entity));
  7431. };
  7432. } else {
  7433. var val = this._value;
  7434. return function (entity) {
  7435. return predFn(v1Fn(entity), val);
  7436. };
  7437. }
  7438. };
  7439. proto.toString = function () {
  7440. return __formatString("{%1} %2 {%3}", this._propertyOrExpr, this._filterQueryOp.operator, this._value);
  7441. };
  7442. proto.validate = function (entityType) {
  7443. // throw if not valid
  7444. this._fnNode1.validate(entityType);
  7445. this.dataType = this._fnNode1.dataType;
  7446. };
  7447. // internal functions
  7448. // TODO: still need to handle localQueryComparisonOptions for guids.
  7449. function getPredicateFn(entityType, filterQueryOp, dataType) {
  7450. var lqco = entityType.metadataStore.localQueryComparisonOptions;
  7451. var mc = getComparableFn(dataType);
  7452. var predFn;
  7453. switch (filterQueryOp) {
  7454. case FilterQueryOp.Equals:
  7455. predFn = function(v1, v2) {
  7456. if (v1 && typeof v1 === 'string') {
  7457. return stringEquals(v1, v2, lqco);
  7458. } else {
  7459. return mc(v1) == mc(v2);
  7460. }
  7461. };
  7462. break;
  7463. case FilterQueryOp.NotEquals:
  7464. predFn = function (v1, v2) {
  7465. if (v1 && typeof v1 === 'string') {
  7466. return !stringEquals(v1, v2, lqco);
  7467. } else {
  7468. return mc(v1) != mc(v2);
  7469. }
  7470. };
  7471. break;
  7472. case FilterQueryOp.GreaterThan:
  7473. predFn = function (v1, v2) { return mc(v1) > mc(v2); };
  7474. break;
  7475. case FilterQueryOp.GreaterThanOrEqual:
  7476. predFn = function (v1, v2) { return mc(v1) >= mc(v2); };
  7477. break;
  7478. case FilterQueryOp.LessThan:
  7479. predFn = function (v1, v2) { return mc(v1) < mc(v2); };
  7480. break;
  7481. case FilterQueryOp.LessThanOrEqual:
  7482. predFn = function (v1, v2) { return mc(v1) <= mc(v2); };
  7483. break;
  7484. case FilterQueryOp.StartsWith:
  7485. predFn = function (v1, v2) { return stringStartsWith(v1, v2, lqco); };
  7486. break;
  7487. case FilterQueryOp.EndsWith:
  7488. predFn = function (v1, v2) { return stringEndsWith(v1, v2, lqco); };
  7489. break;
  7490. case FilterQueryOp.Contains:
  7491. predFn = function (v1, v2) { return stringContains(v1, v2, lqco); };
  7492. break;
  7493. default:
  7494. throw new Error("Unknown FilterQueryOp: " + filterQueryOp);
  7495. }
  7496. return predFn;
  7497. }
  7498. function stringEquals(a, b, lqco) {
  7499. if (b == null) return false;
  7500. if (typeof b !== 'string') {
  7501. b = b.toString();
  7502. }
  7503. if (lqco.usesSql92CompliantStringComparison) {
  7504. a = (a || "").trim();
  7505. b = (b || "").trim();
  7506. }
  7507. if (!lqco.isCaseSensitive) {
  7508. a = (a || "").toLowerCase();
  7509. b = (b || "").toLowerCase();
  7510. }
  7511. return a == b;
  7512. }
  7513. function stringStartsWith(a, b, lqco) {
  7514. if (!lqco.isCaseSensitive) {
  7515. a = (a || "").toLowerCase();
  7516. b = (b || "").toLowerCase();
  7517. }
  7518. return __stringStartsWith(a, b);
  7519. }
  7520. function stringEndsWith(a, b, lqco) {
  7521. if (!lqco.isCaseSensitive) {
  7522. a = (a || "").toLowerCase();
  7523. b = (b || "").toLowerCase();
  7524. }
  7525. return __stringEndsWith(a, b);
  7526. }
  7527. function stringContains(a, b, lqco) {
  7528. if (!lqco.isCaseSensitive) {
  7529. a = (a || "").toLowerCase();
  7530. b = (b || "").toLowerCase();
  7531. }
  7532. return a.indexOf(b) >= 0;
  7533. }
  7534. function formatValue(val, dataType) {
  7535. if (val == null) {
  7536. return null;
  7537. }
  7538. var msg;
  7539. dataType = dataType || DataType.fromValue(val);
  7540. if (dataType.isNumeric) {
  7541. if (typeof val === "string") {
  7542. if (dataType.isInteger) {
  7543. val = parseInt(val);
  7544. } else {
  7545. val = parseFloat(val);
  7546. }
  7547. }
  7548. return val;
  7549. } else if (dataType === DataType.String) {
  7550. return "'" + val + "'";
  7551. } else if (dataType === DataType.DateTime) {
  7552. try {
  7553. return "datetime'" + val.toISOString() + "'";
  7554. } catch(e) {
  7555. msg = __formatString("'%1' is not a valid dateTime", val);
  7556. throw new Error(msg);
  7557. }
  7558. } else if (dataType == DataType.Time) {
  7559. if (!__isDuration(val)) {
  7560. msg = __formatString("'%1' is not a valid ISO 8601 duration", val);
  7561. throw new Error(msg);
  7562. }
  7563. return "time'" + val + "'";
  7564. } else if (dataType === DataType.Guid) {
  7565. if (!__isGuid(val)) {
  7566. msg = __formatString("'%1' is not a valid guid", val);
  7567. throw new Error(msg);
  7568. }
  7569. return "guid'" + val + "'";
  7570. } else if (dataType === DataType.Boolean) {
  7571. if (typeof val === "string") {
  7572. return val.trim().toLowerCase() === "true";
  7573. } else {
  7574. return val;
  7575. }
  7576. } else {
  7577. return val;
  7578. }
  7579. }
  7580. return ctor;
  7581. })();
  7582. // Does not need to be exposed.
  7583. var CompositePredicate = (function () {
  7584. var ctor = function (booleanOperator, predicates) {
  7585. // if debug
  7586. if (!Array.isArray(predicates)) {
  7587. throw new Error("predicates parameter must be an array");
  7588. }
  7589. // end debug
  7590. if ((this.symbol === "not") && (predicates.length !== 1)) {
  7591. throw new Error("Only a single predicate can be passed in with the 'Not' operator");
  7592. }
  7593. this._booleanQueryOp = BooleanQueryOp.from(booleanOperator);
  7594. if (!this._booleanQueryOp) {
  7595. throw new Error("Unknown query operation: " + booleanOperator);
  7596. }
  7597. this._predicates = predicates;
  7598. };
  7599. var proto = new Predicate({ prototype: true });
  7600. ctor.prototype = proto;
  7601. proto.toOdataFragment = function (entityType) {
  7602. if (this._predicates.length == 1) {
  7603. return this._booleanQueryOp.operator + " " + "(" + this._predicates[0].toOdataFragment(entityType) + ")";
  7604. } else {
  7605. var result = this._predicates.map(function (p) {
  7606. return "(" + p.toOdataFragment(entityType) + ")";
  7607. }).join(" " + this._booleanQueryOp.operator + " ");
  7608. return result;
  7609. }
  7610. };
  7611. proto.toFunction = function (entityType) {
  7612. return createFunction(entityType, this._booleanQueryOp, this._predicates);
  7613. };
  7614. proto.toString = function () {
  7615. if (this._predicates.length == 1) {
  7616. return this._booleanQueryOp.operator + " " + "(" + this._predicates[0] + ")";
  7617. } else {
  7618. var result = this._predicates.map(function (p) {
  7619. return "(" + p.toString() + ")";
  7620. }).join(" " + this._booleanQueryOp.operator + " ");
  7621. return result;
  7622. }
  7623. };
  7624. proto.validate = function (entityType) {
  7625. // will throw if not found;
  7626. if (this._isValidated) return;
  7627. this._predicates.every(function (p) {
  7628. p.validate(entityType);
  7629. });
  7630. this._isValidated = true;
  7631. };
  7632. function createFunction(entityType, booleanQueryOp, predicates) {
  7633. var func, funcs;
  7634. switch (booleanQueryOp) {
  7635. case BooleanQueryOp.Not:
  7636. func = predicates[0].toFunction(entityType);
  7637. return function (entity) {
  7638. return !func(entity);
  7639. };
  7640. case BooleanQueryOp.And:
  7641. funcs = predicates.map(function (p) { return p.toFunction(entityType); });
  7642. return function (entity) {
  7643. var result = funcs.reduce(function (prev, cur) {
  7644. return prev && cur(entity);
  7645. }, true);
  7646. return result;
  7647. };
  7648. case BooleanQueryOp.Or:
  7649. funcs = predicates.map(function (p) { return p.toFunction(entityType); });
  7650. return function (entity) {
  7651. var result = funcs.reduce(function (prev, cur) {
  7652. return prev || cur(entity);
  7653. }, false);
  7654. return result;
  7655. };
  7656. default:
  7657. throw new Error("Invalid boolean operator:" + booleanQueryOp);
  7658. }
  7659. }
  7660. return ctor;
  7661. })();
  7662. // Not exposed externally for now
  7663. var OrderByClause = (function () {
  7664. /*
  7665. An OrderByClause is a description of the properties and direction that the result
  7666. of a query should be sorted in. OrderByClauses are immutable, which means that any
  7667. method that would modify an OrderByClause actually returns a new OrderByClause.
  7668. For example for an Employee object with properties of 'Company' and 'LastName' the following would be valid expressions:
  7669. var obc = new OrderByClause("Company.CompanyName, LastName")
  7670. or
  7671. var obc = new OrderByClause("Company.CompanyName desc, LastName")
  7672. or
  7673. var obc = new OrderByClause("Company.CompanyName, LastName", true);
  7674. @class OrderByClause
  7675. */
  7676. /*
  7677. @method <ctor> OrderByClause
  7678. @param propertyPaths {String|Array or String} A ',' delimited string of 'propertyPaths' or an array of property path string. Each 'propertyPath'
  7679. should be a valid property name or property path for the EntityType of the query associated with this clause.
  7680. @param [isDesc=false] {Boolean}
  7681. */
  7682. var ctor = function (propertyPaths, isDesc) {
  7683. if (propertyPaths.prototype === true) {
  7684. // used to construct prototype
  7685. return this;
  7686. }
  7687. return ctor.create(propertyPaths, isDesc);
  7688. };
  7689. var proto = ctor.prototype;
  7690. /*
  7691. Alternative method of creating an OrderByClause.
  7692. Example for an Employee object with properties of 'Company' and 'LastName':
  7693. var obc = OrderByClause.create("Company.CompanyName, LastName")
  7694. or
  7695. var obc = OrderByClause.create("Company.CompanyName desc, LastName")
  7696. or
  7697. var obc = OrderByClause.create("Company.CompanyName, LastName", true);
  7698. @method create
  7699. @static
  7700. @param propertyPaths {Array of String} An array of 'propertyPaths'. Each 'propertyPaths'
  7701. parameter should be a valid property name or property path for the EntityType of the query associated with this clause.
  7702. @param [isDesc=false] {Boolean}
  7703. */
  7704. ctor.create = function (propertyPaths, isDesc) {
  7705. if (propertyPaths.length > 1) {
  7706. var clauses = propertyPaths.map(function (pp) {
  7707. return new SimpleOrderByClause(pp, isDesc);
  7708. });
  7709. return new CompositeOrderByClause(clauses);
  7710. } else {
  7711. return new SimpleOrderByClause(propertyPaths[0], isDesc);
  7712. }
  7713. };
  7714. /*
  7715. Returns a 'composite' OrderByClause by combining other OrderByClauses.
  7716. @method combine
  7717. @static
  7718. @param orderByClauses {Array of OrderByClause}
  7719. */
  7720. ctor.combine = function (orderByClauses) {
  7721. return new CompositeOrderByClause(orderByClauses);
  7722. };
  7723. /*
  7724. Returns whether an object is an OrderByClause.
  7725. @method isOrderByClause
  7726. @static
  7727. @param obj {Object}
  7728. */
  7729. ctor.isOrderByClause = function (obj) {
  7730. return obj instanceof OrderByClause;
  7731. };
  7732. /*
  7733. Returns whether a new OrderByClause with a specified clause add to the end of this one.
  7734. @method addClause
  7735. @param orderByClause {OrderByClause}
  7736. */
  7737. proto.addClause = function (orderByClause) {
  7738. return new CompositeOrderByClause([this, orderByClause]);
  7739. };
  7740. return ctor;
  7741. })();
  7742. // Does not need to be exposed.
  7743. var SimpleOrderByClause = (function () {
  7744. var ctor = function (propertyPath, isDesc) {
  7745. if (!(typeof propertyPath == 'string')) {
  7746. throw new Error("propertyPath is not a string");
  7747. }
  7748. propertyPath = propertyPath.trim();
  7749. var parts = propertyPath.split(' ');
  7750. // parts[0] is the propertyPath; [1] would be whether descending or not.
  7751. if (parts.length > 1 && isDesc !== true && isDesc !== false) {
  7752. isDesc = __stringStartsWith(parts[1].toLowerCase(), "desc");
  7753. if (!isDesc) {
  7754. // isDesc is false but check to make sure its intended.
  7755. var isAsc = __stringStartsWith(parts[1].toLowerCase(), "asc");
  7756. if (!isAsc) {
  7757. throw new Error("the second word in the propertyPath must begin with 'desc' or 'asc'");
  7758. }
  7759. }
  7760. }
  7761. this.propertyPath = parts[0];
  7762. this.isDesc = isDesc;
  7763. };
  7764. var proto = new OrderByClause({ prototype: true });
  7765. ctor.prototype = proto;
  7766. proto.validate = function (entityType) {
  7767. if (!entityType) {
  7768. return;
  7769. } // can't validate yet
  7770. // will throw an exception on bad propertyPath
  7771. this.lastProperty = entityType.getProperty(this.propertyPath, true);
  7772. };
  7773. proto.toOdataFragment = function (entityType) {
  7774. return entityType._clientPropertyPathToServer(this.propertyPath) + (this.isDesc ? " desc" : "");
  7775. };
  7776. proto.getComparer = function () {
  7777. var propertyPath = this.propertyPath;
  7778. var isDesc = this.isDesc;
  7779. var that = this;
  7780. return function (entity1, entity2) {
  7781. var value1 = getPropertyPathValue(entity1, propertyPath);
  7782. var value2 = getPropertyPathValue(entity2, propertyPath);
  7783. var dataType = (that.lastProperty || {}).dataType;
  7784. if (dataType === DataType.String) {
  7785. if (!that.lastProperty.parentType.metadataStore.localQueryComparisonOptions.isCaseSensitive) {
  7786. value1 = (value1 || "").toLowerCase();
  7787. value2 = (value2 || "").toLowerCase();
  7788. }
  7789. } else {
  7790. var normalize = getComparableFn(dataType);
  7791. value1 = normalize(value1);
  7792. value2 = normalize(value2);
  7793. }
  7794. if (value1 == value2) {
  7795. return 0;
  7796. } else if (value1 > value2) {
  7797. return isDesc ? -1 : 1;
  7798. } else {
  7799. return isDesc ? 1 : -1;
  7800. }
  7801. };
  7802. };
  7803. return ctor;
  7804. })();
  7805. // Does not need to be exposed.
  7806. var CompositeOrderByClause = (function () {
  7807. var ctor = function (orderByClauses) {
  7808. var resultClauses = [];
  7809. orderByClauses.forEach(function (obc) {
  7810. if (obc instanceof CompositeOrderByClause) {
  7811. resultClauses = resultClauses.concat(obc.orderByClauses);
  7812. } else if (obc instanceof SimpleOrderByClause) {
  7813. resultClauses.push(obc);
  7814. } else {
  7815. throw new Error("Invalid argument to CompositeOrderByClause ctor.");
  7816. }
  7817. });
  7818. this._orderByClauses = resultClauses;
  7819. };
  7820. var proto = new OrderByClause({ prototype: true });
  7821. ctor.prototype = proto;
  7822. proto.validate = function (entityType) {
  7823. this._orderByClauses.forEach(function (obc) {
  7824. obc.validate(entityType);
  7825. });
  7826. };
  7827. proto.toOdataFragment = function (entityType) {
  7828. var strings = this._orderByClauses.map(function (obc) {
  7829. return obc.toOdataFragment(entityType);
  7830. });
  7831. // should return something like CompanyName,Address/City desc
  7832. return strings.join(',');
  7833. };
  7834. proto.getComparer = function () {
  7835. var orderByFuncs = this._orderByClauses.map(function (obc) {
  7836. return obc.getComparer();
  7837. });
  7838. return function (entity1, entity2) {
  7839. for (var i = 0; i < orderByFuncs.length; i++) {
  7840. var result = orderByFuncs[i](entity1, entity2);
  7841. if (result != 0) {
  7842. return result;
  7843. }
  7844. }
  7845. return 0;
  7846. };
  7847. };
  7848. return ctor;
  7849. })();
  7850. // Not exposed
  7851. var SelectClause = (function () {
  7852. var ctor = function (propertyPaths) {
  7853. this.propertyPaths = propertyPaths;
  7854. this._pathNames = propertyPaths.map(function(pp) {
  7855. return pp.replace(".", "_");
  7856. });
  7857. };
  7858. var proto = ctor.prototype;
  7859. proto.validate = function (entityType) {
  7860. if (!entityType) {
  7861. return;
  7862. } // can't validate yet
  7863. // will throw an exception on bad propertyPath
  7864. this.propertyPaths.forEach(function(path) {
  7865. entityType.getProperty(path, true);
  7866. });
  7867. };
  7868. proto.toOdataFragment = function(entityType) {
  7869. var frag = this.propertyPaths.map(function (pp) {
  7870. return entityType._clientPropertyPathToServer(pp);
  7871. }).join(",");
  7872. return frag;
  7873. };
  7874. proto.toFunction = function (entityType) {
  7875. var that = this;
  7876. return function (entity) {
  7877. var result = {};
  7878. that.propertyPaths.forEach(function (path, i) {
  7879. result[that._pathNames[i]] = getPropertyPathValue(entity, path);
  7880. });
  7881. return result;
  7882. };
  7883. };
  7884. return ctor;
  7885. })();
  7886. // Not exposed
  7887. var ExpandClause = (function () {
  7888. // propertyPaths is an array of strings.
  7889. var ctor = function (propertyPaths) {
  7890. this.propertyPaths = propertyPaths;
  7891. };
  7892. var proto = ctor.prototype;
  7893. // // TODO:
  7894. // proto.validate = function (entityType) {
  7895. //
  7896. // };
  7897. proto.toOdataFragment = function(entityType) {
  7898. var frag = this.propertyPaths.map(function(pp) {
  7899. return entityType._clientPropertyPathToServer(pp);
  7900. }).join(",");
  7901. return frag;
  7902. };
  7903. return ctor;
  7904. })();
  7905. function getPropertyPathValue(obj, propertyPath) {
  7906. var properties;
  7907. if (Array.isArray(propertyPath)) {
  7908. properties = propertyPath;
  7909. } else {
  7910. properties = propertyPath.split(".");
  7911. }
  7912. if (properties.length === 1) {
  7913. return obj.getProperty(propertyPath);
  7914. } else {
  7915. var nextValue = obj;
  7916. for (var i = 0; i < properties.length; i++) {
  7917. nextValue = nextValue.getProperty(properties[i]);
  7918. // == in next line is deliberate - checks for undefined or null.
  7919. if (nextValue == null) {
  7920. break;
  7921. }
  7922. }
  7923. return nextValue;
  7924. }
  7925. }
  7926. function getComparableFn(dataType) {
  7927. if (dataType === DataType.DateTime) {
  7928. // dates don't perform equality comparisons properly
  7929. return function (value) { return value && value.getTime(); };
  7930. } else if (dataType === DataType.Time) {
  7931. // durations must be converted to compare them
  7932. return function(value) { return value && __durationToSeconds(value); };
  7933. } else {
  7934. return function(value) { return value; };
  7935. }
  7936. }
  7937. // Fixup --- because EntityAspect does not have access to EntityQuery or EntityMetadata
  7938. EntityAspect.prototype.loadNavigationProperty = function (navigationProperty, callback, errorCallback) {
  7939. var entity = this.entity;
  7940. var navProperty = entity.entityType._checkNavProperty(navigationProperty);
  7941. var query = EntityQuery.fromEntityNavigation(entity, navProperty, callback, errorCallback);
  7942. return entity.entityAspect.entityManager.executeQuery(query, callback, errorCallback);
  7943. };
  7944. // expose
  7945. // do not expose SimplePredicate and CompositePredicate
  7946. // Note: FnNode only exposed for testing purposes
  7947. breeze.FilterQueryOp = FilterQueryOp;
  7948. breeze.Predicate = Predicate;
  7949. breeze.EntityQuery = EntityQuery;
  7950. breeze.FnNode = FnNode;
  7951. // Not documented - only exposed for testing purposes
  7952. breeze.OrderByClause = OrderByClause;
  7953. /**
  7954. @module breeze
  7955. **/
  7956. var KeyGenerator = function () {
  7957. /*
  7958. @class KeyGenerator
  7959. */
  7960. var ctor = function () {
  7961. // key is dataProperty.name + || + entityType.name, value is propEntry
  7962. // propEntry = { entityType, propertyName, keyMap }
  7963. // keyMap has key of the actual value ( as a string) and a value of null or the real id.
  7964. this._tempIdMap = {};
  7965. this.nextNumber = -1;
  7966. this.nextNumberIncrement = -1;
  7967. this.stringPrefix = "K_";
  7968. };
  7969. var proto = ctor.prototype;
  7970. /*
  7971. Returns a unique 'temporary' id for the specified {{#crossLink "EntityType"}}{{/crossLink}}.
  7972. Uniqueness is defined for this purpose as being unique within each instance of a KeyGenerator. This is sufficient
  7973. because each EntityManager will have its own instance of a KeyGenerator and any entities imported into
  7974. the EntityManager with temporary keys will have them regenerated and remapped on import.
  7975. The return value of this method must be of the correct type as determined by the
  7976. @example
  7977. // Assume em1 is a preexisting EntityManager
  7978. var custType = em1.metadataStore.getEntityType("Customer");
  7979. var cust1 = custType.createEntity();
  7980. // next line both sets cust1's 'CustomerId' property but also returns the value
  7981. var cid1 = em1.generateTempKeyValue(cust1);
  7982. em1.saveChanges()
  7983. .then( function( data) {
  7984. var sameCust1 = data.results[0];
  7985. // cust1 === sameCust1;
  7986. // but cust1.getProperty("CustomerId") != cid1
  7987. // because the server will have generated a new id
  7988. // and the client will have been updated with this
  7989. // new id.
  7990. })
  7991. @method generateTempKeyValue
  7992. @param entityType {EntityType}
  7993. */
  7994. proto.generateTempKeyValue = function (entityType) {
  7995. var keyProps = entityType.keyProperties;
  7996. if (keyProps.length > 1) {
  7997. throw new Error("Ids can not be autogenerated for entities with multipart keys");
  7998. }
  7999. var keyProp = keyProps[0];
  8000. var nextId = getNextId(this, keyProp.dataType);
  8001. var propEntry = getPropEntry(this, keyProp, true);
  8002. propEntry.keyMap[nextId.toString()] = null;
  8003. return nextId;
  8004. };
  8005. proto.getTempKeys = function () {
  8006. var results = [];
  8007. for (var key in this._tempIdMap) {
  8008. var propEntry = this._tempIdMap[key];
  8009. var entityType = propEntry.entityType;
  8010. // var propName = propEntry.propertyName;
  8011. for (var keyValue in propEntry.keyMap) {
  8012. results.push(new EntityKey(entityType, [keyValue]));
  8013. }
  8014. }
  8015. return results;
  8016. };
  8017. // proto methods below are not part of the KeyGenerator interface.
  8018. proto.isTempKey = function (entityKey) {
  8019. var keyProps = entityKey.entityType.keyProperties;
  8020. if (keyProps.length > 1) return false;
  8021. var keyProp = keyProps[0];
  8022. var propEntry = getPropEntry(this, keyProp);
  8023. if (!propEntry) {
  8024. return false;
  8025. }
  8026. return (propEntry.keyMap[entityKey.values[0].toString()] !== undefined);
  8027. };
  8028. function getPropEntry(that, keyProp, createIfMissing) {
  8029. var key = keyProp.name + ".." + keyProp.parentType.name;
  8030. var propEntry = that._tempIdMap[key];
  8031. if (!propEntry) {
  8032. if (createIfMissing) {
  8033. propEntry = { entityType: keyProp.parentType, propertyName: keyProp.name, keyMap: {} };
  8034. that._tempIdMap[key] = propEntry;
  8035. }
  8036. }
  8037. return propEntry;
  8038. }
  8039. function getNextId(that, dataType) {
  8040. if (dataType.isNumeric) {
  8041. return getNextNumber(that);
  8042. }
  8043. if (dataType === DataType.String) {
  8044. return this.stringPrefix + getNextNumber(that).toString();
  8045. }
  8046. if (dataType === DataType.Guid) {
  8047. return __getUuid();
  8048. }
  8049. if (dataType === DataType.DateTime) {
  8050. return Date.now();
  8051. }
  8052. throw new Error("Cannot use a property with a dataType of: " + dataType.toString() + " for id generation");
  8053. }
  8054. function getNextNumber(that) {
  8055. var result = that.nextNumber;
  8056. that.nextNumber += that.nextNumberIncrement;
  8057. return result;
  8058. }
  8059. __config.registerType(ctor, "KeyGenerator");
  8060. return ctor;
  8061. }();
  8062. breeze.KeyGenerator = KeyGenerator;
  8063. breeze.makeRelationArray = function() {
  8064. var relationArrayMixin = {};
  8065. /**
  8066. Relation arrays are not actually classes, they are objects that mimic arrays. A relation array is collection of
  8067. entities associated with a navigation property on a single entity. i.e. customer.orders or order.orderDetails.
  8068. This collection looks like an array in that the basic methods on arrays such as 'push', 'pop', 'shift', 'unshift', 'splice'
  8069. are all provided as well as several special purpose methods.
  8070. @class ?_relationArray_
  8071. **/
  8072. /**
  8073. An {{#crossLink "Event"}}{{/crossLink}} that fires whenever the contents of this array changed. This event
  8074. is fired any time a new entity is attached or added to the EntityManager and happens to belong to this collection.
  8075. Adds that occur as a result of query or import operations are batched so that all of the adds or removes to any individual
  8076. collections are collected into a single notification event for each relation array.
  8077. @example
  8078. // assume order is an order entity attached to an EntityManager.
  8079. orders.arrayChanged.subscribe(
  8080. function (arrayChangedArgs) {
  8081. var addedEntities = arrayChangedArgs.added;
  8082. var removedEntities = arrayChanged.removed;
  8083. });
  8084. @event arrayChanged
  8085. @param added {Array of Entity} An array of all of the entities added to this collection.
  8086. @param removed {Array of Entity} An array of all of the removed from this collection.
  8087. @readOnly
  8088. **/
  8089. relationArrayMixin.push = function() {
  8090. if (this._inProgress) {
  8091. return -1;
  8092. }
  8093. var goodAdds = getGoodAdds(this, Array.prototype.slice.call(arguments));
  8094. if (!goodAdds.length) {
  8095. return this.length;
  8096. }
  8097. var result = Array.prototype.push.apply(this, goodAdds);
  8098. processAdds(this, goodAdds);
  8099. return result;
  8100. };
  8101. relationArrayMixin.unshift = function() {
  8102. var goodAdds = getGoodAdds(this, Array.prototype.slice.call(arguments));
  8103. if (!goodAdds.length) {
  8104. return this.length;
  8105. }
  8106. var result = Array.prototype.unshift.apply(this, goodAdds);
  8107. processAdds(this, Array.prototype.slice.call(goodAdds));
  8108. return result;
  8109. };
  8110. relationArrayMixin.pop = function() {
  8111. var result = Array.prototype.pop.apply(this);
  8112. processRemoves(this, [result]);
  8113. return result;
  8114. };
  8115. relationArrayMixin.shift = function() {
  8116. var result = Array.prototype.shift.apply(this);
  8117. processRemoves(this, [result]);
  8118. return result;
  8119. };
  8120. relationArrayMixin.splice = function() {
  8121. var goodAdds = getGoodAdds(this, Array.prototype.slice.call(arguments, 2));
  8122. var newArgs = Array.prototype.slice.call(arguments, 0, 2).concat(goodAdds);
  8123. var result = Array.prototype.splice.apply(this, newArgs);
  8124. processRemoves(this, result);
  8125. if (goodAdds.length) {
  8126. processAdds(this, goodAdds);
  8127. }
  8128. return result;
  8129. };
  8130. /**
  8131. Performs an asynchronous load of all other the entities associated with this relationArray.
  8132. @example
  8133. // assume orders is an empty, as yet unpopulated, relation array of orders
  8134. // associated with a specific customer.
  8135. orders.load().then(...)
  8136. @method load
  8137. @param [callback] {Function}
  8138. @param [errorCallback] {Function}
  8139. @return {Promise}
  8140. **/
  8141. relationArrayMixin.load = function(callback, errorCallback) {
  8142. var parent = this.parentEntity;
  8143. var query = EntityQuery.fromEntityNavigation(this.parentEntity, this.navigationProperty);
  8144. var em = parent.entityAspect.entityManager;
  8145. return em.executeQuery(query, callback, errorCallback);
  8146. };
  8147. relationArrayMixin._getEventParent = function() {
  8148. return this.parentEntity.entityAspect;
  8149. };
  8150. relationArrayMixin._getPendingPubs = function() {
  8151. var em = this.parentEntity.entityAspect.entityManager;
  8152. return em && em._pendingPubs;
  8153. };
  8154. function getGoodAdds(relationArray, adds) {
  8155. var goodAdds = checkForDups(relationArray, adds);
  8156. if (!goodAdds.length) {
  8157. return goodAdds;
  8158. }
  8159. var parentEntity = relationArray.parentEntity;
  8160. var entityManager = parentEntity.entityAspect.entityManager;
  8161. // we do not want to attach an entity during loading
  8162. // because these will all be 'attached' at a later step.
  8163. if (entityManager && !entityManager.isLoading) {
  8164. goodAdds.forEach(function(add) {
  8165. if (add.entityAspect.entityState.isDetached()) {
  8166. relationArray._inProgress = true;
  8167. try {
  8168. entityManager.attachEntity(add, EntityState.Added);
  8169. } finally {
  8170. relationArray._inProgress = false;
  8171. }
  8172. }
  8173. });
  8174. }
  8175. return goodAdds;
  8176. }
  8177. function checkForDups(relationArray, adds) {
  8178. // don't allow dups in this array. - also prevents recursion
  8179. var inverseProp = relationArray.navigationProperty.inverse;
  8180. var goodAdds = adds.filter(function(a) {
  8181. if (relationArray._addsInProcess.indexOf(a) >= 0) {
  8182. return false;
  8183. }
  8184. var inverseValue = a.getProperty(inverseProp.name);
  8185. return inverseValue != relationArray.parentEntity;
  8186. });
  8187. return goodAdds;
  8188. }
  8189. function processAdds(relationArray, adds) {
  8190. var inp = relationArray.navigationProperty.inverse;
  8191. if (inp) {
  8192. var addsInProcess = relationArray._addsInProcess;
  8193. var startIx = addsInProcess.length;
  8194. try {
  8195. adds.forEach(function(childEntity) {
  8196. addsInProcess.push(childEntity);
  8197. childEntity.setProperty(inp.name, relationArray.parentEntity);
  8198. });
  8199. } finally {
  8200. addsInProcess.splice(startIx, adds.length);
  8201. }
  8202. ;
  8203. }
  8204. // this is referencing the name of the method on the relationArray not the name of the event
  8205. publish(relationArray, "arrayChanged", { relationArray: relationArray, added: adds });
  8206. }
  8207. function publish(publisher, eventName, eventArgs) {
  8208. var pendingPubs = publisher._getPendingPubs();
  8209. if (pendingPubs) {
  8210. if (!publisher._pendingArgs) {
  8211. publisher._pendingArgs = eventArgs;
  8212. pendingPubs.push(function() {
  8213. publisher[eventName].publish(publisher._pendingArgs);
  8214. publisher._pendingArgs = null;
  8215. });
  8216. } else {
  8217. combineArgs(publisher._pendingArgs, eventArgs);
  8218. }
  8219. } else {
  8220. publisher[eventName].publish(eventArgs);
  8221. }
  8222. }
  8223. function combineArgs(target, source) {
  8224. for (var key in source) {
  8225. if (key !== "relationArray" && target.hasOwnProperty(key)) {
  8226. var sourceValue = source[key];
  8227. var targetValue = target[key];
  8228. if (targetValue) {
  8229. if (!Array.isArray(targetValue)) {
  8230. throw new Error("Cannot combine non array args");
  8231. }
  8232. Array.prototype.push.apply(targetValue, sourceValue);
  8233. } else {
  8234. target[key] = sourceValue;
  8235. }
  8236. }
  8237. }
  8238. }
  8239. function processRemoves(relationArray, removes) {
  8240. var inp = relationArray.navigationProperty.inverse;
  8241. if (inp) {
  8242. removes.forEach(function(childEntity) {
  8243. childEntity.setProperty(inp.name, null);
  8244. });
  8245. }
  8246. // this is referencing the name of the method on the relationArray not the name of the event
  8247. publish(relationArray, "arrayChanged", { relationArray: relationArray, removed: removes });
  8248. }
  8249. function makeRelationArray(arr, parentEntity, navigationProperty) {
  8250. arr.parentEntity = parentEntity;
  8251. arr.navigationProperty = navigationProperty;
  8252. arr.arrayChanged = new Event("arrayChanged_entityCollection", arr);
  8253. // array of pushes currently in process on this relation array - used to prevent recursion.
  8254. arr._addsInProcess = [];
  8255. return __extend(arr, relationArrayMixin);
  8256. }
  8257. return makeRelationArray;
  8258. }();
  8259. /**
  8260. @module breeze
  8261. **/
  8262. var EntityManager = (function () {
  8263. /**
  8264. Instances of the EntityManager contain and manage collections of entities, either retrieved from a backend datastore or created on the client.
  8265. @class EntityManager
  8266. **/
  8267. /**
  8268. @example
  8269. At its most basic an EntityManager can be constructed with just a service name
  8270. @example
  8271. var entityManager = new EntityManager( "api/NorthwindIBModel");
  8272. This is the same as calling it with the following configuration object
  8273. @example
  8274. var entityManager = new EntityManager( {serviceName: "api/NorthwindIBModel" });
  8275. Usually however, configuration objects will contain more than just the 'serviceName';
  8276. @example
  8277. var metadataStore = new MetadataStore();
  8278. var entityManager = new EntityManager( {
  8279. serviceName: "api/NorthwindIBModel",
  8280. metadataStore: metadataStore
  8281. });
  8282. or
  8283. @example
  8284. return new QueryOptions({
  8285. mergeStrategy: obj,
  8286. fetchStrategy: this.fetchStrategy
  8287. });
  8288. var queryOptions = new QueryOptions({
  8289. mergeStrategy: MergeStrategy.OverwriteChanges,
  8290. fetchStrategy: FetchStrategy.FromServer
  8291. });
  8292. var validationOptions = new ValidationOptions({
  8293. validateOnAttach: true,
  8294. validateOnSave: true,
  8295. validateOnQuery: false
  8296. });
  8297. var entityManager = new EntityManager({
  8298. serviceName: "api/NorthwindIBModel",
  8299. queryOptions: queryOptions,
  8300. validationOptions: validationOptions
  8301. });
  8302. @method <ctor> EntityManager
  8303. @param [config] {Object|String} Configuration settings or a service name.
  8304. @param [config.serviceName] {String}
  8305. @param [config.dataService] {DataService} An entire DataService (instead of just the serviceName above).
  8306. @param [config.metadataStore=MetadataStore.defaultInstance] {MetadataStore}
  8307. @param [config.queryOptions=QueryOptions.defaultInstance] {QueryOptions}
  8308. @param [config.saveOptions=SaveOptions.defaultInstance] {SaveOptions}
  8309. @param [config.validationOptions=ValidationOptions.defaultInstance] {ValidationOptions}
  8310. @param [config.keyGeneratorCtor] {Function}
  8311. **/
  8312. var ctor = function (config) {
  8313. if (arguments.length > 1) {
  8314. throw new Error("The EntityManager ctor has a single optional argument that is either a 'serviceName' or a configuration object.");
  8315. }
  8316. if (arguments.length === 0) {
  8317. config = { serviceName: "" };
  8318. } else if (typeof config === 'string') {
  8319. config = { serviceName: config };
  8320. }
  8321. assertConfig(config)
  8322. .whereParam("serviceName").isOptional().isString()
  8323. .whereParam("dataService").isOptional().isInstanceOf(DataService)
  8324. .whereParam("metadataStore").isInstanceOf(MetadataStore).isOptional().withDefault(new MetadataStore())
  8325. .whereParam("queryOptions").isInstanceOf(QueryOptions).isOptional().withDefault(QueryOptions.defaultInstance)
  8326. .whereParam("saveOptions").isInstanceOf(SaveOptions).isOptional().withDefault(SaveOptions.defaultInstance)
  8327. .whereParam("validationOptions").isInstanceOf(ValidationOptions).isOptional().withDefault(ValidationOptions.defaultInstance)
  8328. .whereParam("keyGeneratorCtor").isFunction().isOptional().withDefault(KeyGenerator)
  8329. .applyAll(this);
  8330. if (config.serviceName) {
  8331. this.dataService = new DataService({
  8332. serviceName: this.serviceName
  8333. });
  8334. }
  8335. this.serviceName = this.dataService && this.dataService.serviceName;
  8336. this.entityChanged = new Event("entityChanged_entityManager", this);
  8337. this.hasChangesChanged = new Event("hasChangesChanged_entityManager", this);
  8338. this.clear();
  8339. };
  8340. var proto = ctor.prototype;
  8341. proto._$typeName = "EntityManager";
  8342. Event.bubbleEvent(proto, null);
  8343. /**
  8344. The service name associated with this EntityManager.
  8345. __readOnly__
  8346. @property serviceName {String}
  8347. **/
  8348. /**
  8349. The DataService name associated with this EntityManager.
  8350. __readOnly__
  8351. @property dataService {DataService}
  8352. **/
  8353. /**
  8354. The {{#crossLink "MetadataStore"}}{{/crossLink}} associated with this EntityManager.
  8355. __readOnly__
  8356. @property metadataStore {MetadataStore}
  8357. **/
  8358. /**
  8359. The {{#crossLink "QueryOptions"}}{{/crossLink}} associated with this EntityManager.
  8360. __readOnly__
  8361. @property queryOptions {QueryOptions}
  8362. **/
  8363. /**
  8364. The {{#crossLink "SaveOptions"}}{{/crossLink}} associated with this EntityManager.
  8365. __readOnly__
  8366. @property saveOptions {SaveOptions}
  8367. **/
  8368. /**
  8369. The {{#crossLink "ValidationOptions"}}{{/crossLink}} associated with this EntityManager.
  8370. __readOnly__
  8371. @property validationOptions {ValidationOptions}
  8372. **/
  8373. /**
  8374. The {{#crossLink "KeyGenerator"}}{{/crossLink}} constructor associated with this EntityManager.
  8375. __readOnly__
  8376. @property keyGeneratorCtor {KeyGenerator constructor}
  8377. **/
  8378. // events
  8379. /**
  8380. An {{#crossLink "Event"}}{{/crossLink}} that fires whenever a change to any entity in this EntityManager occurs.
  8381. @example
  8382. var em = new EntityManager( {serviceName: "api/NorthwindIBModel" });
  8383. em.entityChanged.subscribe(function(changeArgs) {
  8384. // This code will be executed any time any entity within the entityManager is added, modified, deleted or detached for any reason.
  8385. var action = changeArgs.entityAction;
  8386. var entity = changeArgs.entity;
  8387. // .. do something to this entity when it is changed.
  8388. });
  8389. });
  8390. @event entityChanged
  8391. @param entityAction {EntityAction} The {{#crossLink "EntityAction"}}{{/crossLink}} that occured.
  8392. @param entity {Object} The entity that changed. If this is null, then all entities in the entityManager were affected.
  8393. @param args {Object} Additional information about this event. This will differ based on the entityAction.
  8394. @readOnly
  8395. **/
  8396. // class methods
  8397. /**
  8398. Creates a new entity of a specified type and optionally initializes it. By default the new entity is created with an EntityState of Added
  8399. but you can also optionally specify an EntityState. An EntityState of 'Detached' will insure that the entity is created but not yet added
  8400. to the EntityManager.
  8401. @example
  8402. // assume em1 is an EntityManager containing a number of preexisting entities.
  8403. // create and add an entity;
  8404. var emp1 = em1.createEntity("Employee");
  8405. // create and add an initialized entity;
  8406. var emp2 = em1.createEntity("Employee", { lastName: Smith", firstName: "John" });
  8407. // create and attach (not add) an initialized entity
  8408. var emp3 = em1.createEntity("Employee", { id: 435, lastName: Smith", firstName: "John" }, EntityState.Unchanged);
  8409. // create but don't attach an entity;
  8410. var emp4 = em1.createEntity("Employee", { id: 435, lastName: Smith", firstName: "John" }, EntityState.Detached);
  8411. @method createEntity
  8412. @param typeName {String} The name of the type for which an instance should be created.
  8413. @param [initialValues=null] {Config object} - Configuration object of the properties to set immediately after creation.
  8414. @param [entityState=EntityState.Added] {EntityState} - Configuration object of the properties to set immediately after creation.
  8415. @return {Entity} A new Entity of the specified type.
  8416. **/
  8417. proto.createEntity = function (typeName, initialValues, entityState) {
  8418. entityState = entityState || EntityState.Added;
  8419. var entity = this.metadataStore
  8420. .getEntityType(typeName)
  8421. .createEntity(initialValues);
  8422. if (entityState !== EntityState.Detached) {
  8423. this.attachEntity(entity, entityState);
  8424. }
  8425. return entity;
  8426. };
  8427. /**
  8428. Creates a new EntityManager and imports a previously exported result into it.
  8429. @example
  8430. // assume em1 is an EntityManager containing a number of preexisting entities.
  8431. var bundle = em1.exportEntities();
  8432. // can be stored via the web storage api
  8433. window.localStorage.setItem("myEntityManager", bundle);
  8434. // assume the code below occurs in a different session.
  8435. var bundleFromStorage = window.localStorage.getItem("myEntityManager");
  8436. // and imported
  8437. var em2 = EntityManager.importEntities(bundleFromStorage);
  8438. // em2 will now have a complete copy of what was in em1
  8439. @method importEntities
  8440. @static
  8441. @param exportedString {String} The result of a previous 'exportEntities' call.
  8442. @param [config] {Object} A configuration object.
  8443. @param [config.mergeStrategy] {MergeStrategy} A {{#crossLink "MergeStrategy"}}{{/crossLink}} to use when
  8444. merging into an existing EntityManager.
  8445. @return {EntityManager} A new EntityManager.
  8446. **/
  8447. ctor.importEntities = function (exportedString, config) {
  8448. var em = new EntityManager();
  8449. em.importEntities(exportedString, config);
  8450. return em;
  8451. };
  8452. // instance methods
  8453. /**
  8454. Exports an entire EntityManager or just selected entities into a serialized string for external storage.
  8455. @example
  8456. This method can be used to take a snapshot of an EntityManager that can be either stored offline or held
  8457. memory. This snapshot can be restored or merged into an another EntityManager at some later date.
  8458. @example
  8459. // assume em1 is an EntityManager containing a number of existing entities.
  8460. var bundle = em1.exportEntities();
  8461. // can be stored via the web storage api
  8462. window.localStorage.setItem("myEntityManager", bundle);
  8463. // assume the code below occurs in a different session.
  8464. var bundleFromStorage = window.localStorage.getItem("myEntityManager");
  8465. var em2 = new EntityManager({
  8466. serviceName: em1.serviceName,
  8467. metadataStore: em1.metadataStore
  8468. });
  8469. em2.importEntities(bundleFromStorage);
  8470. // em2 will now have a complete copy of what was in em1
  8471. You can also control exactly which entities are exported.
  8472. @example
  8473. // assume entitiesToExport is an array of entities to export.
  8474. var bundle = em1.exportEntities(entitiesToExport);
  8475. // assume em2 is another entityManager containing some of the same entities possibly with modifications.
  8476. em2.importEntities(bundle, { mergeStrategy: MergeStrategy.PreserveChanges} );
  8477. @method exportEntities
  8478. @param [entities] {Array of entities} The entities to export; all entities are exported if this is omitted.
  8479. @return {String} A serialized version of the exported data.
  8480. **/
  8481. proto.exportEntities = function (entities) {
  8482. var exportBundle = exportEntityGroups(this, entities);
  8483. var json = {
  8484. metadataStore: this.metadataStore.exportMetadata(),
  8485. dataService: this.dataService,
  8486. saveOptions: this.saveOptions,
  8487. queryOptions: this.queryOptions,
  8488. validationOptions: this.validationOptions,
  8489. tempKeys: exportBundle.tempKeys,
  8490. entityGroupMap: exportBundle.entityGroupMap
  8491. };
  8492. var result = JSON.stringify(json, null, __config.stringifyPad);
  8493. return result;
  8494. };
  8495. /**
  8496. Imports a previously exported result into this EntityManager.
  8497. @example
  8498. This method can be used to make a complete copy of any previously created entityManager, even if created
  8499. in a previous session and stored in localStorage. The static version of this method performs a
  8500. very similar process.
  8501. @example
  8502. // assume em1 is an EntityManager containing a number of existing entities.
  8503. var bundle = em1.exportEntities();
  8504. // bundle can be stored in window.localStorage or just held in memory.
  8505. var em2 = new EntityManager({
  8506. serviceName: em1.serviceName,
  8507. metadataStore: em1.metadataStore
  8508. });
  8509. em2.importEntities(bundle);
  8510. // em2 will now have a complete copy of what was in em1
  8511. It can also be used to merge the contents of a previously created EntityManager with an
  8512. existing EntityManager with control over how the two are merged.
  8513. @example
  8514. var bundle = em1.exportEntities();
  8515. // assume em2 is another entityManager containing some of the same entities possibly with modifications.
  8516. em2.importEntities(bundle, { mergeStrategy: MergeStrategy.PreserveChanges} );
  8517. // em2 will now contain all of the entities from both em1 and em2. Any em2 entities with previously
  8518. // made modifications will not have been touched, but all other entities from em1 will have been imported.
  8519. @method importEntities
  8520. @param exportedString {String} The result of a previous 'export' call.
  8521. @param [config] {Object} A configuration object.
  8522. @param [config.mergeStrategy] {MergeStrategy} A {{#crossLink "MergeStrategy"}}{{/crossLink}} to use when
  8523. merging into an existing EntityManager.
  8524. @chainable
  8525. **/
  8526. proto.importEntities = function (exportedString, config) {
  8527. config = config || {};
  8528. assertConfig(config)
  8529. .whereParam("mergeStrategy").isEnumOf(MergeStrategy).isOptional().withDefault(this.queryOptions.mergeStrategy)
  8530. .applyAll(config);
  8531. var that = this;
  8532. var json = JSON.parse(exportedString);
  8533. this.metadataStore.importMetadata(json.metadataStore);
  8534. // the || clause is for backwards compat with an earlier serialization format.
  8535. this.dataService = (json.dataService && DataService.fromJSON(json.dataService)) || new DataService({ serviceName: json.serviceName });
  8536. this.saveOptions = new SaveOptions(json.saveOptions);
  8537. this.queryOptions = QueryOptions.fromJSON(json.queryOptions);
  8538. this.validationOptions = new ValidationOptions(json.validationOptions);
  8539. var tempKeyMap = {};
  8540. json.tempKeys.forEach(function (k) {
  8541. var oldKey = EntityKey.fromJSON(k, that.metadataStore);
  8542. tempKeyMap[oldKey.toString()] = that.keyGenerator.generateTempKeyValue(oldKey.entityType);
  8543. });
  8544. config.tempKeyMap = tempKeyMap;
  8545. __wrapExecution(function() {
  8546. that._pendingPubs = [];
  8547. }, function(state) {
  8548. that._pendingPubs.forEach(function(fn) { fn(); });
  8549. that._pendingPubs = null;
  8550. }, function() {
  8551. __objectForEach(json.entityGroupMap, function(entityTypeName, jsonGroup) {
  8552. var entityType = that.metadataStore.getEntityType(entityTypeName, true);
  8553. var targetEntityGroup = findOrCreateEntityGroup(that, entityType);
  8554. importEntityGroup(targetEntityGroup, jsonGroup, config);
  8555. });
  8556. });
  8557. return this;
  8558. };
  8559. // Similar logic - but more performant is performed inside of importEntityGroup.
  8560. //function dateReviver(key, value) {
  8561. // var a;
  8562. // if (typeof value === 'string') {
  8563. // a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
  8564. // if (a) {
  8565. // return new Date(Date.parse(value));
  8566. // }
  8567. // }
  8568. // return value;
  8569. //};
  8570. /**
  8571. Clears this EntityManager's cache but keeps all other settings. Note that this
  8572. method is not as fast as creating a new EntityManager via 'new EntityManager'.
  8573. This is because clear actually detaches all of the entities from the EntityManager.
  8574. @example
  8575. // assume em1 is an EntityManager containing a number of existing entities.
  8576. em1.clear();
  8577. // em1 is will now contain no entities, but all other setting will be maintained.
  8578. @method clear
  8579. **/
  8580. proto.clear = function () {
  8581. __objectForEach(this._entityGroupMap, function (key, entityGroup) {
  8582. // remove en
  8583. entityGroup._clear();
  8584. });
  8585. this._entityGroupMap = {};
  8586. this._unattachedChildrenMap = new UnattachedChildrenMap();
  8587. this.keyGenerator = new this.keyGeneratorCtor();
  8588. this.entityChanged.publish({ entityAction: EntityAction.Clear });
  8589. if (this._hasChanges) {
  8590. this._hasChanges = false;
  8591. this.hasChangesChanged.publish({ entityManager: this, hasChanges: false });
  8592. }
  8593. };
  8594. /**
  8595. General purpose property set method. Any of the properties documented below
  8596. may be set.
  8597. @example
  8598. // assume em1 is a previously created EntityManager
  8599. // where we want to change some of its settings.
  8600. em1.setProperties( {
  8601. serviceName: "api/foo"
  8602. });
  8603. @method setProperties
  8604. @param config {Object}
  8605. @param [config.serviceName] {String}
  8606. @param [config.dataService] {DataService}
  8607. @param [config.queryOptions] {QueryOptions}
  8608. @param [config.saveOptions] {SaveOptions}
  8609. @param [config.validationOptions] {ValidationOptions}
  8610. @param [config.keyGeneratorCtor] {Function}
  8611. **/
  8612. proto.setProperties = function (config) {
  8613. assertConfig(config)
  8614. .whereParam("serviceName").isString().isOptional()
  8615. .whereParam("dataService").isInstanceOf(DataService).isOptional()
  8616. .whereParam("queryOptions").isInstanceOf(QueryOptions).isOptional()
  8617. .whereParam("saveOptions").isInstanceOf(SaveOptions).isOptional()
  8618. .whereParam("validationOptions").isInstanceOf(ValidationOptions).isOptional()
  8619. .whereParam("keyGeneratorCtor").isOptional()
  8620. .applyAll(this);
  8621. if (config.serviceName) {
  8622. this.dataService = new DataService({
  8623. serviceName: this.serviceName,
  8624. });
  8625. }
  8626. this.serviceName = this.dataService && this.dataService.serviceName;
  8627. if (config.keyGeneratorCtor) {
  8628. this.keyGenerator = new this.keyGeneratorCtor();
  8629. }
  8630. };
  8631. /**
  8632. Creates an empty copy of this EntityManager
  8633. @example
  8634. // assume em1 is an EntityManager containing a number of existing entities.
  8635. var em2 = em1.createEmptyCopy();
  8636. // em2 is a new EntityManager with all of em1's settings
  8637. // but no entities.
  8638. @method createEmptyCopy
  8639. @return {EntityManager} A new EntityManager.
  8640. **/
  8641. proto.createEmptyCopy = function () {
  8642. var copy = new ctor({
  8643. dataService: this.dataService,
  8644. metadataStore: this.metadataStore,
  8645. queryOptions: this.queryOptions,
  8646. saveOptions: this.saveOptions,
  8647. validationOptions: this.validationOptions,
  8648. keyGeneratorCtor: this.keyGeneratorCtor
  8649. });
  8650. return copy;
  8651. };
  8652. /**
  8653. Attaches an entity to this EntityManager with an {{#crossLink "EntityState"}}{{/crossLink}} of 'Added'.
  8654. @example
  8655. // assume em1 is an EntityManager containing a number of existing entities.
  8656. var custType = em1.metadataStore.getEntityType("Customer");
  8657. var cust1 = custType.createEntity();
  8658. em1.addEntity(cust1);
  8659. Note that this is the same as using 'attachEntity' with an {{#crossLink "EntityState"}}{{/crossLink}} of 'Added'.
  8660. @example
  8661. // assume em1 is an EntityManager containing a number of existing entities.
  8662. var custType = em1.metadataStore.getEntityType("Customer");
  8663. var cust1 = custType.createEntity();
  8664. em1.attachEntity(cust1, EntityState.Added);
  8665. @method addEntity
  8666. @param entity {Entity} The entity to add.
  8667. @return {Entity} The added entity.
  8668. **/
  8669. proto.addEntity = function (entity) {
  8670. return this.attachEntity(entity, EntityState.Added);
  8671. };
  8672. /**
  8673. Attaches an entity to this EntityManager with a specified {{#crossLink "EntityState"}}{{/crossLink}}.
  8674. @example
  8675. // assume em1 is an EntityManager containing a number of existing entities.
  8676. var custType = em1.metadataStore.getEntityType("Customer");
  8677. var cust1 = custType.createEntity();
  8678. em1.attachEntity(cust1, EntityState.Added);
  8679. @method attachEntity
  8680. @param entity {Entity} The entity to add.
  8681. @param [entityState=EntityState.Unchanged] {EntityState} The EntityState of the newly attached entity. If omitted this defaults to EntityState.Unchanged.
  8682. @return {Entity} The attached entity.
  8683. **/
  8684. proto.attachEntity = function (entity, entityState) {
  8685. assertParam(entity, "entity").isRequired().check();
  8686. this.metadataStore._checkEntityType(entity);
  8687. entityState = assertParam(entityState, "entityState").isEnumOf(EntityState).isOptional().check(EntityState.Unchanged);
  8688. if (entity.entityType.metadataStore != this.metadataStore) {
  8689. throw new Error("Cannot attach this entity because the EntityType and MetadataStore associated with this entity does not match this EntityManager's MetadataStore.");
  8690. }
  8691. var aspect = entity.entityAspect;
  8692. if (!aspect) {
  8693. aspect = new EntityAspect(entity);
  8694. aspect._postInitialize(entity);
  8695. }
  8696. var manager = aspect.entityManager;
  8697. if (manager) {
  8698. if (manager == this) {
  8699. return entity;
  8700. } else {
  8701. throw new Error("This entity already belongs to another EntityManager");
  8702. }
  8703. }
  8704. var that = this;
  8705. __using(this, "isLoading", true, function () {
  8706. if (entityState.isAdded()) {
  8707. checkEntityKey(that, entity);
  8708. }
  8709. attachEntityCore(that, entity, entityState);
  8710. attachRelatedEntities(that, entity, entityState);
  8711. });
  8712. if (this.validationOptions.validateOnAttach) {
  8713. entity.entityAspect.validateEntity();
  8714. }
  8715. if (!entityState.isUnchanged()) {
  8716. this._notifyStateChange(entity, true);
  8717. }
  8718. this.entityChanged.publish({ entityAction: EntityAction.Attach, entity: entity });
  8719. return entity;
  8720. };
  8721. /**
  8722. Detaches an entity from this EntityManager.
  8723. @example
  8724. // assume em1 is an EntityManager containing a number of existing entities.
  8725. // assume cust1 is a customer Entity previously attached to em1
  8726. em1.detachEntity(cust1);
  8727. // em1 will now no longer contain cust1 and cust1 will have an
  8728. // entityAspect.entityState of EntityState.Detached
  8729. @method detachEntity
  8730. @param entity {Entity} The entity to detach.
  8731. @return {Boolean} Whether the entity could be detached. This will return false if the entity is already detached or was never attached.
  8732. **/
  8733. proto.detachEntity = function (entity) {
  8734. assertParam(entity, "entity").isEntity().check();
  8735. var aspect = entity.entityAspect;
  8736. if (!aspect) {
  8737. // no aspect means in couldn't appear in any group
  8738. return false;
  8739. }
  8740. var group = aspect.entityGroup;
  8741. if (!group) {
  8742. // no group === already detached.
  8743. return false;
  8744. }
  8745. if (group.entityManager !== this) {
  8746. throw new Error("This entity does not belong to this EntityManager.");
  8747. }
  8748. group.detachEntity(entity);
  8749. aspect._removeFromRelations();
  8750. this.entityChanged.publish({ entityAction: EntityAction.Detach, entity: entity });
  8751. aspect._detach();
  8752. return true;
  8753. };
  8754. /**
  8755. Fetches the metadata associated with the EntityManager's current 'serviceName'. This call
  8756. occurs internally before the first query to any service if the metadata hasn't already been
  8757. loaded.
  8758. @example
  8759. Usually you will not actually process the results of a fetchMetadata call directly, but will instead
  8760. ask for the metadata from the EntityManager after the fetchMetadata call returns.
  8761. @example
  8762. var em1 = new EntityManager( "api/NorthwindIBModel");
  8763. em1.fetchMetadata()
  8764. .then(function() {
  8765. var metadataStore = em1.metadataStore;
  8766. // do something with the metadata
  8767. }
  8768. .fail(function(exception) {
  8769. // handle exception here
  8770. };
  8771. @method fetchMetadata
  8772. @async
  8773. @param [callback] {Function} Function called on success.
  8774. successFunction([schema])
  8775. @param [callback.schema] {Object} The raw Schema object from metadata provider - Because this schema will differ depending on the metadata provider
  8776. it is usually better to access metadata via the 'metadataStore' property of the EntityManager after this method's Promise or callback completes.
  8777. @param [errorCallback] {Function} Function called on failure.
  8778. failureFunction([error])
  8779. @param [errorCallback.error] {Error} Any error that occured wrapped into an Error object.
  8780. @return {Promise} Promise
  8781. promiseData.schema {Object} The raw Schema object from metadata provider - Because this schema will differ depending on the metadata provider
  8782. it is usually better to access metadata via the 'metadataStore' property of the EntityManager instead of using this 'raw' data.
  8783. **/
  8784. proto.fetchMetadata = function (callback, errorCallback) {
  8785. assertParam(callback, "callback").isFunction().isOptional().check();
  8786. assertParam(errorCallback, "errorCallback").isFunction().isOptional().check();
  8787. var promise = this.metadataStore.fetchMetadata(this.dataService);
  8788. // TODO: WARNING: DO NOT LEAVE THIS CODE IN PRODUCTION.
  8789. // TEST::: see if serialization actually works completely
  8790. // var that = this;
  8791. // promise = promise.then(function () {
  8792. // var stringified = that.metadataStore.exportMetadata();
  8793. // that.metadataStore = new MetadataStore();
  8794. // that.metadataStore.importMetadata(stringified);
  8795. // });
  8796. return promiseWithCallbacks(promise, callback, errorCallback);
  8797. };
  8798. /**
  8799. Executes the specified query.
  8800. @example
  8801. This method can be called using a 'promises' syntax ( recommended)
  8802. @example
  8803. var em = new EntityManager(serviceName);
  8804. var query = new EntityQuery("Orders");
  8805. em.executeQuery(query)
  8806. .then( function(data) {
  8807. var orders = data.results;
  8808. ... query results processed here
  8809. }).fail( function(err) {
  8810. ... query failure processed here
  8811. });
  8812. or with callbacks
  8813. @example
  8814. var em = new EntityManager(serviceName);
  8815. var query = new EntityQuery("Orders");
  8816. em.executeQuery(query,
  8817. function(data) {
  8818. var orders = data.results;
  8819. ... query results processed here
  8820. },
  8821. function(err) {
  8822. ... query failure processed here
  8823. });
  8824. Either way this method is the same as calling the The {{#crossLink "EntityQuery"}}{{/crossLink}} 'execute' method.
  8825. @example
  8826. var em = new EntityManager(serviceName);
  8827. var query = new EntityQuery("Orders").using(em);
  8828. query.execute()
  8829. .then( function(data) {
  8830. var orders = data.results;
  8831. ... query results processed here
  8832. }).fail( function(err) {
  8833. ... query failure processed here
  8834. });
  8835. @method executeQuery
  8836. @async
  8837. @param query {EntityQuery|String} The {{#crossLink "EntityQuery"}}{{/crossLink}} or OData query string to execute.
  8838. @param [callback] {Function} Function called on success.
  8839. successFunction([data])
  8840. @param callback.data {Object}
  8841. @param callback.data.results {Array of Entity}
  8842. @param callback.data.query {EntityQuery} The original query
  8843. @param callback.data.XHR {XMLHttpRequest} The raw XMLHttpRequest returned from the server.
  8844. @param callback.data.inlineCount {Integer} Only available if 'inlineCount(true)' was applied to the query. Returns the count of
  8845. items that would have been returned by the query before applying any skip or take operators, but after any filter/where predicates
  8846. would have been applied.
  8847. @param [errorCallback] {Function} Function called on failure.
  8848. failureFunction([error])
  8849. @param [errorCallback.error] {Error} Any error that occured wrapped into an Error object.
  8850. @param [errorCallback.error.query] The query that caused the error.
  8851. @param [errorCallback.error.XHR] {XMLHttpRequest} The raw XMLHttpRequest returned from the server.
  8852. @return {Promise} Promise
  8853. promiseData.results {Array of Entity}
  8854. promiseData.query {EntityQuery} The original query
  8855. promiseData.XHR {XMLHttpRequest} The raw XMLHttpRequest returned from the server.
  8856. promiseData.inlineCount {Integer} Only available if 'inlineCount(true)' was applied to the query. Returns the count of
  8857. items that would have been returned by the query before applying any skip or take operators, but after any filter/where predicates
  8858. would have been applied.
  8859. **/
  8860. proto.executeQuery = function (query, callback, errorCallback) {
  8861. // TODO: think about creating an executeOdataQuery or executeRawOdataQuery as a seperate method.
  8862. assertParam(query, "query").isInstanceOf(EntityQuery).or().isString().check();
  8863. assertParam(callback, "callback").isFunction().isOptional().check();
  8864. assertParam(errorCallback, "errorCallback").isFunction().isOptional().check();
  8865. var promise;
  8866. if ( (!this.dataService.hasServerMetadata ) || this.metadataStore.hasMetadataFor(this.dataService.serviceName)) {
  8867. promise = executeQueryCore(this, query);
  8868. } else {
  8869. var that = this;
  8870. promise = this.fetchMetadata().then(function () {
  8871. return executeQueryCore(that, query);
  8872. }).fail(function (error) {
  8873. return Q.reject(error);
  8874. });
  8875. }
  8876. return promiseWithCallbacks(promise, callback, errorCallback);
  8877. };
  8878. /**
  8879. Executes the specified query against this EntityManager's local cache.
  8880. @example
  8881. Because this method is executed immediately there is no need for a promise or a callback
  8882. @example
  8883. var em = new EntityManager(serviceName);
  8884. var query = new EntityQuery("Orders");
  8885. var orders = em.executeQueryLocally(query);
  8886. Note that this can also be accomplished using the 'executeQuery' method with
  8887. a FetchStrategy of FromLocalCache and making use of the Promise or callback
  8888. @example
  8889. var em = new EntityManager(serviceName);
  8890. var query = new EntityQuery("Orders").using(FetchStrategy.FromLocalCache);
  8891. em.executeQuery(query)
  8892. .then( function(data) {
  8893. var orders = data.results;
  8894. ... query results processed here
  8895. }).fail( function(err) {
  8896. ... query failure processed here
  8897. });
  8898. @method executeQueryLocally
  8899. @param query {EntityQuery} The {{#crossLink "EntityQuery"}}{{/crossLink}} to execute.
  8900. @return {Array of Entity} Array of Entities
  8901. **/
  8902. proto.executeQueryLocally = function (query) {
  8903. assertParam(query, "query").isInstanceOf(EntityQuery).check();
  8904. var result;
  8905. var metadataStore = this.metadataStore;
  8906. var entityType = query._getFromEntityType(metadataStore, true);
  8907. // TODO: there may be multiple groups once we go further with inheritence
  8908. var group = findOrCreateEntityGroup(this, entityType);
  8909. // filter then order then skip then take
  8910. var filterFunc = query._toFilterFunction(entityType);
  8911. if (filterFunc) {
  8912. var undeletedFilterFunc = function(entity) {
  8913. return entity && (!entity.entityAspect.entityState.isDeleted()) && filterFunc(entity);
  8914. };
  8915. result = group._entities.filter(undeletedFilterFunc);
  8916. } else {
  8917. result = group._entities.filter(function(entity) {
  8918. return entity && (!entity.entityAspect.entityState.isDeleted());
  8919. });
  8920. }
  8921. var orderByComparer = query._toOrderByComparer(entityType);
  8922. if (orderByComparer) {
  8923. result.sort(orderByComparer);
  8924. }
  8925. var skipCount = query.skipCount;
  8926. if (skipCount) {
  8927. result = result.slice(skipCount);
  8928. }
  8929. var takeCount = query.takeCount;
  8930. if (takeCount) {
  8931. result = result.slice(0, takeCount);
  8932. }
  8933. var selectClause = query.selectClause;
  8934. if (selectClause) {
  8935. var selectFn = selectClause.toFunction();
  8936. result = result.map(function(e) {
  8937. return selectFn(e);
  8938. });
  8939. }
  8940. return result;
  8941. };
  8942. /**
  8943. Saves either a list of specified entities or all changed entities within this EntityManager. If there are no changes to any of the entities
  8944. specified then there will be no server side call made but a valid 'empty' saveResult will still be returned.
  8945. @example
  8946. Often we will be saving all of the entities within an EntityManager that are either added, modified or deleted
  8947. and we will let the 'saveChanges' call determine which entities these are.
  8948. @example
  8949. // assume em1 is an EntityManager containing a number of preexisting entities.
  8950. // This could include added, modified and deleted entities.
  8951. em.saveChanges().then(function(saveResult) {
  8952. var savedEntities = saveResult.entities;
  8953. var keyMappings = saveResult.keyMappings;
  8954. }).fail(function (e) {
  8955. // e is any exception that was thrown.
  8956. });
  8957. But we can also control exactly which entities to save and can specify specific SaveOptions
  8958. @example
  8959. // assume entitiesToSave is an array of entities to save.
  8960. var saveOptions = new SaveOptions({ allowConcurrentSaves: true });
  8961. em.saveChanges(entitiesToSave, saveOptions).then(function(saveResult) {
  8962. var savedEntities = saveResult.entities;
  8963. var keyMappings = saveResult.keyMappings;
  8964. }).fail(function (e) {
  8965. // e is any exception that was thrown.
  8966. });
  8967. Callback methods can also be used
  8968. @example
  8969. em.saveChanges(entitiesToSave, null,
  8970. function(saveResult) {
  8971. var savedEntities = saveResult.entities;
  8972. var keyMappings = saveResult.keyMappings;
  8973. }, function (e) {
  8974. // e is any exception that was thrown.
  8975. }
  8976. );
  8977. @method saveChanges
  8978. @async
  8979. @param [entities] {Array of Entity} The list of entities to save. All entities with changes
  8980. within this EntityManager will be saved if this parameter is omitted, null or empty.
  8981. @param [saveOptions] {SaveOptions} {{#crossLink "SaveOptions"}}{{/crossLink}} for the save - will default to
  8982. {{#crossLink "EntityManager/saveOptions"}}{{/crossLink}} if null.
  8983. @param [callback] {Function} Function called on success.
  8984. successFunction([saveResult])
  8985. @param [callback.saveResult] {Object}
  8986. @param [callback.saveResult.entities] {Array of Entity} The saved entities - with any temporary keys converted into 'real' keys.
  8987. These entities are actually references to entities in the EntityManager cache that have been updated as a result of the
  8988. save.
  8989. @param [callback.saveResult.keyMappings] {Object} Map of OriginalEntityKey, NewEntityKey
  8990. @param [callback.saveResult.XHR] {XMLHttpRequest} The raw XMLHttpRequest returned from the server.
  8991. @param [errorCallback] {Function} Function called on failure.
  8992. failureFunction([error])
  8993. @param [errorCallback.error] {Error} Any error that occured wrapped into an Error object.
  8994. @param [errorCallback.error.XHR] {XMLHttpRequest} The raw XMLHttpRequest returned from the server.
  8995. @return {Promise} Promise
  8996. **/
  8997. proto.saveChanges = function (entities, saveOptions, callback, errorCallback) {
  8998. assertParam(entities, "entities").isOptional().isArray().isEntity().check();
  8999. assertParam(saveOptions, "saveOptions").isInstanceOf(SaveOptions).isOptional().check();
  9000. assertParam(callback, "callback").isFunction().isOptional().check();
  9001. assertParam(errorCallback, "errorCallback").isFunction().isOptional().check();
  9002. saveOptions = saveOptions || this.saveOptions || SaveOptions.defaultInstance;
  9003. var isFullSave = entities == null;
  9004. var entitiesToSave = getEntitiesToSave(this, entities);
  9005. if (entitiesToSave.length == 0) {
  9006. var saveResult = { entities: [], keyMappings: [] };
  9007. if (callback) callback(saveResult);
  9008. return Q.resolve(saveResult);
  9009. }
  9010. if (!saveOptions.allowConcurrentSaves) {
  9011. var anyPendingSaves = entitiesToSave.some(function (entity) {
  9012. return entity.entityAspect.isBeingSaved;
  9013. });
  9014. if (anyPendingSaves) {
  9015. var err = new Error("ConcurrentSaves not allowed - SaveOptions.allowConcurrentSaves is false");
  9016. if (errorCallback) errorCallback(err);
  9017. return Q.reject(err);
  9018. }
  9019. }
  9020. if (this.validationOptions.validateOnSave) {
  9021. var failedEntities = entitiesToSave.filter(function (entity) {
  9022. var aspect = entity.entityAspect;
  9023. var isValid = aspect.entityState.isDeleted() || aspect.validateEntity();
  9024. return !isValid;
  9025. });
  9026. if (failedEntities.length > 0) {
  9027. var valError = new Error("Validation error");
  9028. valError.entitiesWithErrors = failedEntities;
  9029. if (errorCallback) errorCallback(valError);
  9030. return Q.reject(valError);
  9031. }
  9032. }
  9033. updateConcurrencyProperties(entitiesToSave);
  9034. // TODO: need to check that if we are doing a partial save that all entities whose temp keys
  9035. // are referenced are also in the partial save group
  9036. var saveBundle = { entities: unwrapEntities(entitiesToSave, this.metadataStore), saveOptions: saveOptions};
  9037. var saveBundleStringified = JSON.stringify(saveBundle);
  9038. var deferred = Q.defer();
  9039. this.dataService.adapterInstance.saveChanges(this, saveBundleStringified, deferred.resolve, deferred.reject);
  9040. var that = this;
  9041. return deferred.promise.then(function (rawSaveResult) {
  9042. // HACK: simply to change the 'case' of properties in the saveResult
  9043. // but KeyMapping properties are still ucase. ugh...
  9044. var saveResult = { entities: rawSaveResult.Entities, keyMappings: rawSaveResult.KeyMappings, XHR: rawSaveResult.XHR };
  9045. fixupKeys(that, saveResult.keyMappings);
  9046. var queryContext = {
  9047. query: null, // tells visitAndMerge that this is a save instead of a query
  9048. entityManager: that,
  9049. jsonResultsAdapter: that.dataService.jsonResultsAdapter,
  9050. mergeStrategy: MergeStrategy.OverwriteChanges,
  9051. refMap: {},
  9052. deferredFns: []
  9053. };
  9054. var savedEntities = saveResult.entities.map(function (rawEntity) {
  9055. return visitAndMerge(rawEntity, queryContext, { nodeType: "root" });
  9056. });
  9057. markIsBeingSaved(entitiesToSave, false);
  9058. // update _hasChanges after save.
  9059. that._hasChanges = (isFullSave && haveSameContents(entitiesToSave, savedEntities)) ? false : that._hasChangesCore();
  9060. if (!that._hasChanges) {
  9061. that.hasChangesChanged.publish({ entityManager: that, hasChanges: false });
  9062. }
  9063. saveResult.entities = savedEntities;
  9064. if (callback) callback(saveResult);
  9065. return Q.resolve(saveResult);
  9066. }, function (error) {
  9067. markIsBeingSaved(entitiesToSave, false);
  9068. if (errorCallback) errorCallback(error);
  9069. return Q.reject(error);
  9070. });
  9071. };
  9072. function haveSameContents(arr1, arr2) {
  9073. if (arr1.length != arr2.length) {
  9074. return false;
  9075. }
  9076. for (var i=0, c=arr1.length; i<c; i++) {
  9077. if (arr1[i] !== arr2[i]) return false;
  9078. }
  9079. return true;
  9080. }
  9081. // TODO: make this internal - no good reason to expose the EntityGroup to the external api yet.
  9082. proto.findEntityGroup = function (entityType) {
  9083. assertParam(entityType, "entityType").isInstanceOf(EntityType).check();
  9084. return this._entityGroupMap[entityType.name];
  9085. };
  9086. /**
  9087. Attempts to locate an entity within this EntityManager by its key.
  9088. @example
  9089. // assume em1 is an EntityManager containing a number of preexisting entities.
  9090. var employee = em1.getEntityByKey("Employee", 1);
  9091. // employee will either be an entity or null.
  9092. @method getEntityByKey
  9093. @param typeName {String} The entityType name for this key.
  9094. @param keyValues {Object|Array of Object} The values for this key - will usually just be a single value; an array is only needed for multipart keys.
  9095. @return {Entity} An Entity or null;
  9096. **/
  9097. /**
  9098. Attempts to locate an entity within this EntityManager by its {{#crossLink "EntityKey"}}{{/crossLink}}.
  9099. @example
  9100. // assume em1 is an EntityManager containing a number of preexisting entities.
  9101. var employeeType = em1.metadataStore.getEntityType("Employee");
  9102. var employeeKey = new EntityKey(employeeType, 1);
  9103. var employee = em1.getEntityByKey(employeeKey);
  9104. // employee will either be an entity or null.
  9105. @method getEntityByKey - overload
  9106. @param entityKey {EntityKey} The {{#crossLink "EntityKey"}}{{/crossLink}} of the Entity to be located.
  9107. @return {Entity} An Entity or null;
  9108. **/
  9109. proto.getEntityByKey = function () {
  9110. var entityKey = createEntityKey(this, arguments).entityKey;
  9111. var group = this.findEntityGroup(entityKey.entityType);
  9112. if (!group) {
  9113. return null;
  9114. }
  9115. return group.findEntityByKey(entityKey);
  9116. };
  9117. /**
  9118. Attempts to fetch an entity from the server by its key with
  9119. an option to check the local cache first. Note the this EntityManager's queryOptions.mergeStrategy
  9120. will be used to merge any server side entity returned by this method.
  9121. @example
  9122. // assume em1 is an EntityManager containing a number of preexisting entities.
  9123. var employeeType = em1.metadataStore.getEntityType("Employee");
  9124. var employeeKey = new EntityKey(employeeType, 1);
  9125. em1.fetchEntityByKey(employeeKey).then(function(result) {
  9126. var employee = result.entity;
  9127. var entityKey = result.entityKey;
  9128. var fromCache = result.fromCache;
  9129. });
  9130. @method fetchEntityByKey
  9131. @async
  9132. @param typeName {String} The entityType name for this key.
  9133. @param keyValues {Object|Array of Object} The values for this key - will usually just be a single value; an array is only needed for multipart keys.
  9134. @param checkLocalCacheFirst {Boolean=false} Whether to check this EntityManager first before going to the server. By default, the query will NOT do this.
  9135. @return {Promise}
  9136. promiseData.entity {Object} The entity returned or null
  9137. promiseData.entityKey {EntityKey} The entityKey of the entity to fetch.
  9138. promiseData.fromCache {Boolean} Whether this entity was fetched from the server or was found in the local cache.
  9139. **/
  9140. /**
  9141. Attempts to fetch an entity from the server by its {{#crossLink "EntityKey"}}{{/crossLink}} with
  9142. an option to check the local cache first.
  9143. @example
  9144. // assume em1 is an EntityManager containing a number of preexisting entities.
  9145. var employeeType = em1.metadataStore.getEntityType("Employee");
  9146. var employeeKey = new EntityKey(employeeType, 1);
  9147. em1.fetchEntityByKey(employeeKey).then(function(result) {
  9148. var employee = result.entity;
  9149. var entityKey = result.entityKey;
  9150. var fromCache = result.fromCache;
  9151. });
  9152. @method fetchEntityByKey - overload
  9153. @async
  9154. @param entityKey {EntityKey} The {{#crossLink "EntityKey"}}{{/crossLink}} of the Entity to be located.
  9155. @param checkLocalCacheFirst {Boolean=false} Whether to check this EntityManager first before going to the server. By default, the query will NOT do this.
  9156. @return {Promise}
  9157. promiseData.entity {Object} The entity returned or null
  9158. promiseData.entityKey {EntityKey} The entityKey of the entity to fetch.
  9159. promiseData.fromCache {Boolean} Whether this entity was fetched from the server or was found in the local cache.
  9160. **/
  9161. proto.fetchEntityByKey = function () {
  9162. var tpl = createEntityKey(this, arguments);
  9163. var entityKey = tpl.entityKey;
  9164. var checkLocalCacheFirst = tpl.remainingArgs.length === 0 ? false : !!tpl.remainingArgs[0];
  9165. var entity;
  9166. var isDeleted = false;
  9167. if (checkLocalCacheFirst) {
  9168. entity = this.getEntityByKey(entityKey);
  9169. isDeleted = entity && entity.entityAspect.entityState.isDeleted();
  9170. if (isDeleted) {
  9171. entity = null;
  9172. if (this.queryOptions.mergeStrategy === MergeStrategy.OverwriteChanges) {
  9173. isDeleted = false;
  9174. }
  9175. }
  9176. }
  9177. if (entity || isDeleted) {
  9178. return Q.resolve({ entity: entity, entityKey: entityKey, fromCache: true });
  9179. } else {
  9180. return EntityQuery.fromEntityKey(entityKey).using(this).execute().then(function(data) {
  9181. entity = (data.results.length === 0) ? null : data.results[0];
  9182. return Q.resolve({ entity: entity, entityKey: entityKey, fromCache: false });
  9183. });
  9184. }
  9185. };
  9186. /**
  9187. Attempts to locate an entity within this EntityManager by its {{#crossLink "EntityKey"}}{{/crossLink}}.
  9188. @example
  9189. // assume em1 is an EntityManager containing a number of preexisting entities.
  9190. var employeeType = em1.metadataStore.getEntityType("Employee");
  9191. var employeeKey = new EntityKey(employeeType, 1);
  9192. var employee = em1.findEntityByKey(employeeKey);
  9193. // employee will either be an entity or null.
  9194. @method findEntityByKey
  9195. @deprecated - use getEntityByKey instead
  9196. @param entityKey {EntityKey} The {{#crossLink "EntityKey"}}{{/crossLink}} of the Entity to be located.
  9197. @return {Entity} An Entity or null;
  9198. **/
  9199. proto.findEntityByKey = function (entityKey) {
  9200. return this.getEntityByKey(entityKey);
  9201. };
  9202. /**
  9203. Generates a temporary key for the specified entity. This is used to insure that newly
  9204. created entities have unique keys and to register that these keys are temporary and
  9205. need to be automatically replaced with 'real' key values once these entities are saved.
  9206. The EntityManager.keyGeneratorCtor property is used internally by this method to actually generate
  9207. the keys - See the {{#crossLink "~keyGenerator-interface"}}{{/crossLink}} interface description to see
  9208. how a custom key generator can be plugged in.
  9209. @example
  9210. // assume em1 is an EntityManager containing a number of preexisting entities.
  9211. var custType = em1.metadataStore.getEntityType("Customer");
  9212. var custumer = custType.createEntity();
  9213. var customerId = em.generateTempKeyValue(custumer);
  9214. // The 'customer' entity 'CustomerID' property is now set to a newly generated unique id value
  9215. // This property will change again after a successful save of the 'customer' entity.
  9216. em1.saveChanges()
  9217. .then( function( data) {
  9218. var sameCust1 = data.results[0];
  9219. // cust1 === sameCust1;
  9220. // but cust1.getProperty("CustomerId") != customerId
  9221. // because the server will have generated a new id
  9222. // and the client will have been updated with this
  9223. // new id.
  9224. })
  9225. @method generateTempKeyValue
  9226. @param entity {Entity} The Entity to generate a key for.
  9227. @return {Object} The new key value
  9228. **/
  9229. proto.generateTempKeyValue = function (entity) {
  9230. // TODO - check if this entity is attached to this EntityManager.
  9231. assertParam(entity, "entity").isEntity().check();
  9232. var entityType = entity.entityType;
  9233. var nextKeyValue = this.keyGenerator.generateTempKeyValue(entityType);
  9234. var keyProp = entityType.keyProperties[0];
  9235. entity.setProperty(keyProp.name, nextKeyValue);
  9236. entity.entityAspect.hasTempKey = true;
  9237. return nextKeyValue;
  9238. };
  9239. /**
  9240. Returns whether there are any changed entities of the specified {{#crossLink "EntityType"}}{{/crossLink}}s. A 'changed' Entity has
  9241. has an {{#crossLink "EntityState"}}{{/crossLink}} of either Added, Modified or Deleted.
  9242. @example
  9243. This method can be used to determine if an EntityManager has any changes
  9244. @example
  9245. // assume em1 is an EntityManager containing a number of preexisting entities.
  9246. if ( em1.hasChanges() {
  9247. // do something interesting
  9248. }
  9249. or if it has any changes on to a specific {{#crossLink "EntityType"}}{{/crossLink}}
  9250. @example
  9251. // assume em1 is an EntityManager containing a number of preexisting entities.
  9252. var custType = em1.metadataStore.getEntityType("Customer");
  9253. if ( em1.hasChanges(custType) {
  9254. // do something interesting
  9255. }
  9256. or to a collection of {{#crossLink "EntityType"}}{{/crossLink}}s
  9257. @example
  9258. // assume em1 is an EntityManager containing a number of preexisting entities.
  9259. var custType = em1.metadataStore.getEntityType("Customer");
  9260. var orderType = em1.metadataStore.getEntityType("Order");
  9261. if ( em1.hasChanges( [custType, orderType]) {
  9262. // do something interesting
  9263. }
  9264. @method hasChanges
  9265. @param [entityTypes] {String|Array of String|EntityType|Array of EntityType} The {{#crossLink "EntityType"}}{{/crossLink}}s for which 'changed' entities will be found.
  9266. If this parameter is omitted, all EntityTypes are searched. String parameters are treated as EntityType names.
  9267. @return {Boolean} Whether there were any changed entities.
  9268. **/
  9269. proto.hasChanges = function (entityTypes) {
  9270. if (!this._hasChanges) return false;
  9271. if (entityTypes === undefined) return this._hasChanges;
  9272. return this._hasChangesCore(entityTypes);
  9273. };
  9274. /**
  9275. An {{#crossLink "Event"}}{{/crossLink}} that fires whenever an EntityManager transitions to or from having changes.
  9276. @example
  9277. var em = new EntityManager( {serviceName: "api/NorthwindIBModel" });
  9278. em.hasChangesChanged.subscribe(function(args) {
  9279. var hasChangesChanged = args.hasChanges;
  9280. var entityManager = args.entityManager;
  9281. });
  9282. });
  9283. @event hasChangesChanged
  9284. @param entityManager {EntityManager} The EntityManager whose 'hasChanges' status has changed.
  9285. @param hasChanges {Boolean} Whether or not this EntityManager has changes.
  9286. @readOnly
  9287. **/
  9288. // backdoor the "really" check for changes.
  9289. proto._hasChangesCore = function(entityTypes) {
  9290. entityTypes = checkEntityTypes(this, entityTypes);
  9291. var entityGroups = getEntityGroups(this, entityTypes);
  9292. return entityGroups.some(function(eg) {
  9293. return eg.hasChanges();
  9294. });
  9295. };
  9296. /**
  9297. Returns a array of all changed entities of the specified {{#crossLink "EntityType"}}{{/crossLink}}s. A 'changed' Entity has
  9298. has an {{#crossLink "EntityState"}}{{/crossLink}} of either Added, Modified or Deleted.
  9299. @example
  9300. This method can be used to get all of the changed entities within an EntityManager
  9301. @example
  9302. // assume em1 is an EntityManager containing a number of preexisting entities.
  9303. var changedEntities = em1.getChanges();
  9304. or you can specify that you only want the changes on a specific {{#crossLink "EntityType"}}{{/crossLink}}
  9305. @example
  9306. // assume em1 is an EntityManager containing a number of preexisting entities.
  9307. var custType = em1.metadataStore.getEntityType("Customer");
  9308. var changedCustomers = em1.getChanges(custType);
  9309. or to a collection of {{#crossLink "EntityType"}}{{/crossLink}}s
  9310. @example
  9311. // assume em1 is an EntityManager containing a number of preexisting entities.
  9312. var custType = em1.metadataStore.getEntityType("Customer");
  9313. var orderType = em1.metadataStore.getEntityType("Order");
  9314. var changedCustomersAndOrders = em1.getChanges([custType, orderType]);
  9315. @method getChanges
  9316. @param [entityTypes] {String|Array of String|EntityType|Array of EntityType} The {{#crossLink "EntityType"}}{{/crossLink}}s for which 'changed' entities will be found.
  9317. If this parameter is omitted, all EntityTypes are searched. String parameters are treated as EntityType names.
  9318. @return {Array of Entity} Array of Entities
  9319. **/
  9320. proto.getChanges = function (entityTypes) {
  9321. entityTypes = checkEntityTypes(this, entityTypes);
  9322. var entityStates = [EntityState.Added, EntityState.Modified, EntityState.Deleted];
  9323. return this._getEntitiesCore(entityTypes, entityStates);
  9324. };
  9325. /**
  9326. Rejects (reverses the effects) all of the additions, modifications and deletes from this EntityManager.
  9327. @example
  9328. // assume em1 is an EntityManager containing a number of preexisting entities.
  9329. var entities = em1.rejectChanges();
  9330. @method rejectChanges
  9331. @return {Array of Entity} The entities whose changes were rejected. These entities will all have EntityStates of
  9332. either 'Unchanged' or 'Detached'
  9333. **/
  9334. proto.rejectChanges = function () {
  9335. if (!this._hasChanges) return [];
  9336. var entityStates = [EntityState.Added, EntityState.Modified, EntityState.Deleted];
  9337. var changes = this._getEntitiesCore(null, entityStates);
  9338. // next line stops individual reject changes from each calling _hasChangesCore
  9339. this._hasChanges = false;
  9340. changes.forEach(function(e) {
  9341. e.entityAspect.rejectChanges();
  9342. });
  9343. this.hasChangesChanged.publish({ entityManager: this, hasChanges: false });
  9344. return changes;
  9345. };
  9346. /**
  9347. Returns a array of all entities of the specified {{#crossLink "EntityType"}}{{/crossLink}}s with the specified {{#crossLink "EntityState"}}{{/crossLink}}s.
  9348. @example
  9349. This method can be used to get all of the entities within an EntityManager
  9350. @example
  9351. // assume em1 is an EntityManager containing a number of preexisting entities.
  9352. var entities = em1.getEntities();
  9353. or you can specify that you only want the changes on a specific {{#crossLink "EntityType"}}{{/crossLink}}
  9354. @example
  9355. // assume em1 is an EntityManager containing a number of preexisting entities.
  9356. var custType = em1.metadataStore.getEntityType("Customer");
  9357. var customers = em1.getEntities(custType);
  9358. or to a collection of {{#crossLink "EntityType"}}{{/crossLink}}s
  9359. @example
  9360. // assume em1 is an EntityManager containing a number of preexisting entities.
  9361. var custType = em1.metadataStore.getEntityType("Customer");
  9362. var orderType = em1.metadataStore.getEntityType("Order");
  9363. var customersAndOrders = em1.getChanges([custType, orderType]);
  9364. You can also ask for entities with a particular {{#crossLink "EntityState"}}{{/crossLink}} or EntityStates.
  9365. @example
  9366. // assume em1 is an EntityManager containing a number of preexisting entities.
  9367. var custType = em1.metadataStore.getEntityType("Customer");
  9368. var orderType = em1.metadataStore.getEntityType("Order");
  9369. var addedCustomersAndOrders = em1.getEntities([custType, orderType], EntityState.Added);
  9370. @method getEntities
  9371. @param [entityTypes] {String|Array of String|EntityType|Array of EntityType} The {{#crossLink "EntityType"}}{{/crossLink}}s for which entities will be found.
  9372. If this parameter is omitted, all EntityTypes are searched. String parameters are treated as EntityType names.
  9373. @param [entityState] {EntityState|Array of EntityState} The {{#crossLink "EntityState"}}{{/crossLink}}s for which entities will be found.
  9374. If this parameter is omitted, entities of all EntityStates are returned.
  9375. @return {Array of Entity} Array of Entities
  9376. **/
  9377. proto.getEntities = function (entityTypes, entityStates) {
  9378. entityTypes = checkEntityTypes(this, entityTypes);
  9379. assertParam(entityStates, "entityStates").isOptional().isEnumOf(EntityState).or().isNonEmptyArray().isEnumOf(EntityState).check();
  9380. if (entityStates) {
  9381. entityStates = validateEntityStates(this, entityStates);
  9382. }
  9383. return this._getEntitiesCore(entityTypes, entityStates);
  9384. };
  9385. // takes in entityTypes as either strings or entityTypes or arrays of either
  9386. // and returns either an entityType or an array of entityTypes or throws an error
  9387. function checkEntityTypes(em, entityTypes) {
  9388. assertParam(entityTypes, "entityTypes").isString().isOptional().or().isNonEmptyArray().isString()
  9389. .or().isInstanceOf(EntityType).or().isNonEmptyArray().isInstanceOf(EntityType).check();
  9390. if (typeof entityTypes === "string") {
  9391. entityTypes = em.metadataStore.getEntityType(entityTypes, false);
  9392. } else if (Array.isArray(entityTypes) && typeof entityTypes[0] === "string") {
  9393. entityTypes = entityTypes.map(function(etName) {
  9394. return em.metadataStore.getEntityType(etName, false);
  9395. });
  9396. }
  9397. return entityTypes;
  9398. }
  9399. // protected methods
  9400. proto._notifyStateChange = function (entity, needsSave) {
  9401. this.entityChanged.publish({ entityAction: EntityAction.EntityStateChange, entity: entity });
  9402. if (needsSave) {
  9403. if (!this._hasChanges) {
  9404. this._hasChanges = true;
  9405. this.hasChangesChanged.publish({ entityManager: this, hasChanges: true });
  9406. }
  9407. } else {
  9408. // called when rejecting a change or merging an unchanged record.
  9409. if (this._hasChanges) {
  9410. // NOTE: this can be slow with lots of entities in the cache.
  9411. this._hasChanges = this._hasChangesCore();
  9412. if (!this._hasChanges) {
  9413. this.hasChangesChanged.publish({ entityManager: this, hasChanges: false });
  9414. }
  9415. }
  9416. }
  9417. };
  9418. proto._getEntitiesCore = function (entityTypes, entityStates) {
  9419. var entityGroups = getEntityGroups(this, entityTypes);
  9420. // TODO: think about writing a core.mapMany method if we see more of these.
  9421. var selected;
  9422. entityGroups.forEach(function (eg) {
  9423. // eg may be undefined or null
  9424. if (!eg) return;
  9425. var entities = eg.getEntities(entityStates);
  9426. if (!selected) {
  9427. selected = entities;
  9428. } else {
  9429. selected.push.apply(selected, entities);
  9430. }
  9431. });
  9432. return selected || [];
  9433. };
  9434. proto._addUnattachedChild = function (parentEntityKey, navigationProperty, child) {
  9435. var key = parentEntityKey.toString();
  9436. var children = this._unattachedChildrenMap[key];
  9437. if (!children) {
  9438. children = [];
  9439. this._unattachedChildrenMap[key] = children;
  9440. }
  9441. children.push(child);
  9442. };
  9443. proto._linkRelatedEntities = function (entity) {
  9444. var em = this;
  9445. var entityAspect = entity.entityAspect;
  9446. // we do not want entityState to change as a result of linkage.
  9447. __using(em, "isLoading", true, function () {
  9448. var entityType = entity.entityType;
  9449. var navigationProperties = entityType.navigationProperties;
  9450. var unattachedMap = em._unattachedChildrenMap;
  9451. navigationProperties.forEach(function (np) {
  9452. if (np.isScalar) {
  9453. var value = entity.getProperty(np.name);
  9454. // property is already linked up
  9455. if (value) return;
  9456. }
  9457. // first determine if np contains a parent or child
  9458. // having a parentKey means that this is a child
  9459. var parentKey = entityAspect.getParentKey(np);
  9460. if (parentKey) {
  9461. // check for empty keys - meaning that parent id's are not yet set.
  9462. if (parentKey._isEmpty()) return;
  9463. // if a child - look for parent in the em cache
  9464. var parent = em.findEntityByKey(parentKey);
  9465. if (parent) {
  9466. // if found hook it up
  9467. entity.setProperty(np.name, parent);
  9468. } else {
  9469. // else add parent to unresolvedParentMap;
  9470. unattachedMap.addChild(parentKey, np, entity);
  9471. }
  9472. } else {
  9473. // if a parent - look for unresolved children associated with this entity
  9474. // and hook them up.
  9475. var entityKey = entityAspect.getKey();
  9476. var inverseNp = np.inverse;
  9477. if (!inverseNp) return;
  9478. var unattachedChildren = unattachedMap.getChildren(entityKey, inverseNp);
  9479. if (!unattachedChildren) return;
  9480. if (np.isScalar) {
  9481. var onlyChild = unattachedChildren[0];
  9482. entity.setProperty(np.name, onlyChild);
  9483. onlyChild.setProperty(inverseNp.name, entity);
  9484. } else {
  9485. var currentChildren = entity.getProperty(np.name);
  9486. unattachedChildren.forEach(function (child) {
  9487. currentChildren.push(child);
  9488. child.setProperty(inverseNp.name, entity);
  9489. });
  9490. }
  9491. unattachedMap.removeChildren(entityKey, np);
  9492. }
  9493. });
  9494. });
  9495. };
  9496. // private fns
  9497. function createEntityKey(em, args) {
  9498. if (args[0] instanceof EntityKey) {
  9499. return { entityKey: args[0], remainingArgs: Array.prototype.slice.call(args, 1) };
  9500. } else if (typeof args[0] === 'string' && args.length >= 2) {
  9501. var entityType = em.metadataStore.getEntityType(args[0], false);
  9502. return { entityKey: new EntityKey(entityType, args[1]), remainingArgs: Array.prototype.slice.call(args, 2) };
  9503. } else {
  9504. throw new Error("This method requires as its initial parameters either an EntityKey or an entityType name followed by a value or an array of values.");
  9505. }
  9506. }
  9507. function markIsBeingSaved(entities, flag) {
  9508. entities.forEach(function(entity) {
  9509. entity.entityAspect.isBeingSaved = flag;
  9510. });
  9511. }
  9512. function exportEntityGroups(em, entities) {
  9513. var entityGroupMap;
  9514. if (entities) {
  9515. // group entities by entityType and
  9516. // create 'groups' that look like entityGroups.
  9517. entityGroupMap = {};
  9518. entities.forEach(function (e) {
  9519. var group = entityGroupMap[e.entityType.name];
  9520. if (!group) {
  9521. group = {};
  9522. group.entityType = e.entityType;
  9523. group._entities = [];
  9524. entityGroupMap[e.entityType.name] = group;
  9525. }
  9526. group._entities.push(e);
  9527. });
  9528. } else {
  9529. entityGroupMap = em._entityGroupMap;
  9530. }
  9531. var tempKeys = [];
  9532. var newGroupMap = {};
  9533. __objectForEach(entityGroupMap, function (entityTypeName, entityGroup) {
  9534. newGroupMap[entityTypeName] = exportEntityGroup(entityGroup, tempKeys);
  9535. });
  9536. return { entityGroupMap: newGroupMap, tempKeys: tempKeys };
  9537. }
  9538. function exportEntityGroup(entityGroup, tempKeys) {
  9539. var resultGroup = {};
  9540. var entityType = entityGroup.entityType;
  9541. var dpNames = entityType.dataProperties.map(__pluck("name"));
  9542. resultGroup.dataPropertyNames = dpNames;
  9543. var rawEntities = [];
  9544. entityGroup._entities.forEach(function (e) {
  9545. if (e) {
  9546. var rawEntity = [];
  9547. dpNames.forEach(function(dpName) {
  9548. rawEntity.push(e.getProperty(dpName));
  9549. });
  9550. var aspect = e.entityAspect;
  9551. var entityState = aspect.entityState;
  9552. var newAspect = {
  9553. tempNavPropNames: exportTempKeyInfo(aspect, tempKeys),
  9554. entityState: entityState.name
  9555. };
  9556. if (entityState.isModified() || entityState.isDeleted()) {
  9557. newAspect.originalValuesMap = aspect.originalValues;
  9558. }
  9559. rawEntity.push(newAspect);
  9560. rawEntities.push(rawEntity);
  9561. }
  9562. });
  9563. resultGroup.entities = rawEntities;
  9564. return resultGroup;
  9565. }
  9566. function exportTempKeyInfo(entityAspect, tempKeys) {
  9567. var entity = entityAspect.entity;
  9568. if (entityAspect.hasTempKey) {
  9569. tempKeys.push(entityAspect.getKey().toJSON());
  9570. }
  9571. // create map for this entity with foreignKeys that are 'temporary'
  9572. // map -> key: tempKey, value: fkPropName
  9573. var tempNavPropNames;
  9574. entity.entityType.navigationProperties.forEach(function (np) {
  9575. if (np.relatedDataProperties) {
  9576. var relatedValue = entity.getProperty(np.name);
  9577. if (relatedValue && relatedValue.entityAspect.hasTempKey) {
  9578. tempNavPropNames = tempNavPropNames || [];
  9579. tempNavPropNames.push(np.name);
  9580. }
  9581. }
  9582. });
  9583. return tempNavPropNames;
  9584. }
  9585. function importEntityGroup(entityGroup, jsonGroup, config) {
  9586. var tempKeyMap = config.tempKeyMap;
  9587. var entityType = entityGroup.entityType;
  9588. var shouldOverwrite = config.mergeStrategy === MergeStrategy.OverwriteChanges;
  9589. var targetEntity = null;
  9590. var dpNames = jsonGroup.dataPropertyNames;
  9591. var dataProperties = dpNames.map(function(dpName) {
  9592. return entityType.getProperty(dpName);
  9593. });
  9594. var keyIxs = entityType.keyProperties.map(function (kp) {
  9595. return dpNames.indexOf(kp.name);
  9596. });
  9597. var lastIx = dpNames.length;
  9598. var entityChanged = entityGroup.entityManager.entityChanged;
  9599. jsonGroup.entities.forEach(function (rawEntity) {
  9600. var newAspect = rawEntity[lastIx];
  9601. var keyValues = keyIxs.map(function (ix) { return rawEntity[ix]; });
  9602. var entityKey = new EntityKey(entityType, keyValues);
  9603. var entityState = EntityState.fromName(newAspect.entityState);
  9604. var newTempKeyValue;
  9605. if (entityState.isAdded()) {
  9606. newTempKeyValue = tempKeyMap[entityKey.toString()];
  9607. if (newTempKeyValue === undefined) {
  9608. // merge added records with non temp keys
  9609. targetEntity = entityGroup.findEntityByKey(entityKey);
  9610. } else {
  9611. targetEntity = null;
  9612. }
  9613. } else {
  9614. targetEntity = entityGroup.findEntityByKey(entityKey);
  9615. }
  9616. if (targetEntity) {
  9617. var wasUnchanged = targetEntity.entityAspect.entityState.isUnchanged();
  9618. if (shouldOverwrite || wasUnchanged) {
  9619. dataProperties.forEach(function (dp, ix) {
  9620. if (dp.dataType == DataType.DateTime) {
  9621. targetEntity.setProperty(dp.name, new Date(Date.parse(rawEntity[ix])));
  9622. } else {
  9623. targetEntity.setProperty(dp.name, rawEntity[ix]);
  9624. }
  9625. });
  9626. entityChanged.publish({ entityAction: EntityAction.MergeOnImport, entity: targetEntity });
  9627. if (wasUnchanged) {
  9628. if (!entityState.isUnchanged()) {
  9629. entityGroup.entityManager._notifyStateChange(targetEntity, true);
  9630. }
  9631. } else {
  9632. if (entityState.isUnchanged()) {
  9633. entityGroup.entityManager._notifyStateChange(targetEntity, false);
  9634. }
  9635. }
  9636. } else {
  9637. targetEntity = null;
  9638. }
  9639. } else {
  9640. targetEntity = entityType._createEntityCore();
  9641. dataProperties.forEach(function (dp, ix) {
  9642. if (dp.dataType == DataType.DateTime) {
  9643. targetEntity.setProperty(dp.name, new Date(Date.parse(rawEntity[ix])));
  9644. } else {
  9645. targetEntity.setProperty(dp.name, rawEntity[ix]);
  9646. }
  9647. });
  9648. if (newTempKeyValue !== undefined) {
  9649. // fixup pk
  9650. targetEntity.setProperty(entityType.keyProperties[0].name, newTempKeyValue);
  9651. // fixup foreign keys
  9652. if (newAspect.tempNavPropNames) {
  9653. newAspect.tempNavPropNames.forEach(function (npName) {
  9654. var np = entityType.getNavigationProperty(npName);
  9655. var fkPropName = np.relatedDataProperties[0].name;
  9656. var oldFkValue = targetEntity.getProperty(fkPropName);
  9657. var fk = new EntityKey(np.entityType, [oldFkValue]);
  9658. var newFkValue = tempKeyMap[fk.toString()];
  9659. targetEntity.setProperty(fkPropName, newFkValue);
  9660. });
  9661. }
  9662. }
  9663. targetEntity.entityAspect._postInitialize();
  9664. targetEntity = entityGroup.attachEntity(targetEntity, entityState);
  9665. if (entityChanged) {
  9666. entityChanged.publish({ entityAction: EntityAction.AttachOnImport, entity: targetEntity });
  9667. if (!entityState.isUnchanged()) {
  9668. entityGroup.entityManager._notifyStateChange(targetEntity, true);
  9669. }
  9670. }
  9671. }
  9672. if (targetEntity) {
  9673. targetEntity.entityAspect.entityState = entityState;
  9674. if (entityState.isModified()) {
  9675. targetEntity.entityAspect.originalValuesMap = newAspect.originalValues;
  9676. }
  9677. entityGroup.entityManager._linkRelatedEntities( targetEntity);
  9678. }
  9679. });
  9680. }
  9681. function promiseWithCallbacks(promise, callback, errorCallback) {
  9682. promise = promise.then(function (data) {
  9683. if (callback) callback(data);
  9684. return Q.resolve(data);
  9685. }).fail(function (error) {
  9686. if (errorCallback) errorCallback(error);
  9687. return Q.reject(error);
  9688. });
  9689. return promise;
  9690. }
  9691. function getEntitiesToSave(em, entities) {
  9692. var entitiesToSave;
  9693. if (entities) {
  9694. entitiesToSave = entities.filter(function (e) {
  9695. if (e.entityAspect.entityManager !== em) {
  9696. throw new Error("Only entities in this entityManager may be saved");
  9697. }
  9698. return !e.entityAspect.entityState.isDetached();
  9699. });
  9700. } else {
  9701. entitiesToSave = em.getChanges();
  9702. }
  9703. return entitiesToSave;
  9704. }
  9705. function fixupKeys(em, keyMappings) {
  9706. keyMappings.forEach(function (km) {
  9707. var entityTypeName = EntityType._getNormalizedTypeName(km.EntityTypeName);
  9708. var group = em._entityGroupMap[entityTypeName];
  9709. group._fixupKey(km.TempValue, km.RealValue);
  9710. });
  9711. }
  9712. function getEntityGroups(em, entityTypes) {
  9713. var groupMap = em._entityGroupMap;
  9714. if (entityTypes) {
  9715. if (entityTypes instanceof EntityType) {
  9716. return [groupMap[entityTypes.name]];
  9717. } else if (Array.isArray(entityTypes)) {
  9718. return entityTypes.map(function (et) {
  9719. if (et instanceof EntityType) {
  9720. return groupMap[et.name];
  9721. } else {
  9722. throw createError();
  9723. }
  9724. });
  9725. } else {
  9726. throw createError();
  9727. }
  9728. } else {
  9729. return __getOwnPropertyValues(groupMap);
  9730. }
  9731. function createError() {
  9732. return new Error("The EntityManager.getChanges() 'entityTypes' parameter must be either an entityType or an array of entityTypes or null");
  9733. }
  9734. }
  9735. function checkEntityKey(em, entity) {
  9736. var ek = entity.entityAspect.getKey();
  9737. // return properties that are = to defaultValues
  9738. var keyPropsWithDefaultValues = __arrayZip(entity.entityType.keyProperties, ek.values, function (kp, kv) {
  9739. return (kp.defaultValue === kv) ? kp : null;
  9740. }).filter(function (kp) {
  9741. return kp !== null;
  9742. });
  9743. if (keyPropsWithDefaultValues.length) {
  9744. if (entity.entityType.autoGeneratedKeyType !== AutoGeneratedKeyType.None) {
  9745. em.generateTempKeyValue(entity);
  9746. } else {
  9747. // we will allow attaches of entities where only part of the key is set.
  9748. if (keyPropsWithDefaultValues.length === ek.values.length) {
  9749. throw new Error("Cannot attach an object to an EntityManager without first setting its key or setting its entityType 'AutoGeneratedKeyType' property to something other than 'None'");
  9750. }
  9751. }
  9752. }
  9753. }
  9754. function validateEntityStates(em, entityStates) {
  9755. if (!entityStates) return null;
  9756. if (EntityState.contains(entityStates)) {
  9757. entityStates = [entityStates];
  9758. } else if (Array.isArray(entityStates)) {
  9759. entityStates.forEach(function (es) {
  9760. if (!EntityState.contains(es)) {
  9761. throw createError();
  9762. }
  9763. });
  9764. } else {
  9765. throw createError();
  9766. }
  9767. return entityStates;
  9768. function createError() {
  9769. return new Error("The EntityManager.getChanges() 'entityStates' parameter must either be null, an entityState or an array of entityStates");
  9770. }
  9771. }
  9772. function attachEntityCore(em, entity, entityState) {
  9773. var group = findOrCreateEntityGroup(em, entity.entityType);
  9774. group.attachEntity(entity, entityState);
  9775. em._linkRelatedEntities(entity);
  9776. }
  9777. function attachRelatedEntities(em, entity, entityState) {
  9778. var navProps = entity.entityType.navigationProperties;
  9779. navProps.forEach(function (np) {
  9780. var related = entity.getProperty(np.name);
  9781. if (np.isScalar) {
  9782. if (!related) return;
  9783. em.attachEntity(related, entityState);
  9784. } else {
  9785. related.forEach(function (e) {
  9786. em.attachEntity(e, entityState);
  9787. });
  9788. }
  9789. });
  9790. }
  9791. // returns a promise
  9792. function executeQueryCore(em, query) {
  9793. try {
  9794. var metadataStore = em.metadataStore;
  9795. var dataService = query.dataService || em.dataService;
  9796. if (metadataStore.isEmpty() && dataService.hasServerMetadata) {
  9797. throw new Error("cannot execute _executeQueryCore until metadataStore is populated.");
  9798. }
  9799. var queryOptions = query.queryOptions || em.queryOptions || QueryOptions.defaultInstance;
  9800. if (queryOptions.fetchStrategy == FetchStrategy.FromLocalCache) {
  9801. return Q.fcall(function () {
  9802. var results = em.executeQueryLocally(query);
  9803. return { results: results, query: query };
  9804. });
  9805. }
  9806. // _getJsonResultsAdapter does not exist on raw OData queries
  9807. var jsonResultsAdapter = (query._getJsonResultsAdapter && query._getJsonResultsAdapter(em)) || dataService.jsonResultsAdapter;
  9808. var odataQuery = toOdataQueryString(query, metadataStore);
  9809. var queryContext = {
  9810. query: query,
  9811. entityManager: em,
  9812. dataService: dataService,
  9813. mergeStrategy: queryOptions.mergeStrategy,
  9814. jsonResultsAdapter: jsonResultsAdapter,
  9815. refMap: {},
  9816. deferredFns: []
  9817. };
  9818. var deferred = Q.defer();
  9819. var validateOnQuery = em.validationOptions.validateOnQuery;
  9820. var promise = deferred.promise;
  9821. dataService.adapterInstance.executeQuery(em, odataQuery, function (data) {
  9822. var result = __wrapExecution(function () {
  9823. var state = { isLoading: em.isLoading };
  9824. em.isLoading = true;
  9825. em._pendingPubs = [];
  9826. return state;
  9827. }, function (state) {
  9828. // cleanup
  9829. em.isLoading = state.isLoading;
  9830. em._pendingPubs.forEach(function(fn) { fn(); });
  9831. em._pendingPubs = null;
  9832. // HACK for GC
  9833. query = null;
  9834. queryContext = null;
  9835. // HACK: some errors thrown in next function do not propogate properly - this catches them.
  9836. if (state.error) deferred.reject(state.error);
  9837. }, function () {
  9838. var nodes = jsonResultsAdapter.extractResults(data);
  9839. if (!Array.isArray(nodes)) {
  9840. nodes = [nodes];
  9841. }
  9842. var results = nodes.map(function (node) {
  9843. var r = visitAndMerge(node, queryContext, { nodeType: "root" });
  9844. // anon types and simple types will not have an entityAspect.
  9845. if (validateOnQuery && r.entityAspect) {
  9846. r.entityAspect.validateEntity();
  9847. }
  9848. return r;
  9849. });
  9850. if (queryContext.deferredFns.length > 0) {
  9851. queryContext.deferredFns.forEach(function(fn) {
  9852. fn();
  9853. });
  9854. }
  9855. return { results: results, query: query, XHR: data.XHR, inlineCount: data.inlineCount };
  9856. });
  9857. deferred.resolve( result);
  9858. }, function (e) {
  9859. if (e) {
  9860. e.query = query;
  9861. }
  9862. deferred.reject(e);
  9863. });
  9864. return promise;
  9865. } catch (e) {
  9866. if (e) {
  9867. e.query = query;
  9868. }
  9869. return Q.reject(e);
  9870. }
  9871. }
  9872. function visitAndMerge(node, queryContext, nodeContext) {
  9873. nodeContext = nodeContext || {};
  9874. var meta = queryContext.jsonResultsAdapter.visitNode(node, queryContext, nodeContext);
  9875. if (queryContext.query && nodeContext.isTopLevel && !meta.entityType) {
  9876. meta.entityType = queryContext.query._getToEntityType && queryContext.query._getToEntityType(queryContext.entityManager.metadataStore);
  9877. }
  9878. return processMeta(node, queryContext, meta);
  9879. }
  9880. function processMeta(node, queryContext, meta, assignFn) {
  9881. // == is deliberate here instead of ===
  9882. if (meta.ignore || node == null) {
  9883. return null;
  9884. } else if (meta.nodeRefId) {
  9885. var refValue = resolveRefEntity(meta.nodeRefId, queryContext);
  9886. if (typeof refValue == "function") {
  9887. queryContext.deferredFns.push(function () {
  9888. assignFn(refValue);
  9889. });
  9890. return undefined; // deferred and will be set later;
  9891. }
  9892. return refValue;
  9893. } else if (meta.entityType) {
  9894. return mergeEntity(node, queryContext, meta);
  9895. } else {
  9896. // updating the refMap for entities is handled by updateEntityRef for entities.
  9897. if (meta.nodeId) {
  9898. queryContext.refMap[meta.nodeId] = node;
  9899. }
  9900. if (typeof node === 'object') {
  9901. return processAnonType(node, queryContext);
  9902. } else {
  9903. return node;
  9904. }
  9905. }
  9906. }
  9907. function resolveRefEntity(nodeRefId, queryContext) {
  9908. var entity = queryContext.refMap[nodeRefId];
  9909. if (entity === undefined) {
  9910. return function () { return queryContext.refMap[nodeRefId]; };
  9911. } else {
  9912. return entity;
  9913. }
  9914. }
  9915. function mergeEntity(node, queryContext, meta) {
  9916. node._$meta = meta;
  9917. var entityType = meta.entityType;
  9918. node.entityType = entityType;
  9919. var em = queryContext.entityManager;
  9920. var mergeStrategy = queryContext.mergeStrategy;
  9921. var isSaving = queryContext.query == null;
  9922. var entityKey = EntityKey._fromRawEntity(node, entityType);
  9923. var targetEntity = em.findEntityByKey(entityKey);
  9924. if (targetEntity) {
  9925. if (isSaving && targetEntity.entityAspect.entityState.isDeleted()) {
  9926. em.detachEntity(targetEntity);
  9927. return targetEntity;
  9928. }
  9929. var targetEntityState = targetEntity.entityAspect.entityState;
  9930. if (mergeStrategy === MergeStrategy.OverwriteChanges
  9931. || targetEntityState.isUnchanged()) {
  9932. updateEntity(targetEntity, node, queryContext);
  9933. targetEntity.entityAspect.wasLoaded = true;
  9934. targetEntity.entityAspect.entityState = EntityState.Unchanged;
  9935. targetEntity.entityAspect.originalValues = {};
  9936. targetEntity.entityAspect.propertyChanged.publish({ entity: targetEntity, propertyName: null });
  9937. var action = isSaving ? EntityAction.MergeOnSave : EntityAction.MergeOnQuery;
  9938. em.entityChanged.publish({ entityAction: action, entity: targetEntity });
  9939. // this is needed to handle an overwrite or a modified entity with an unchanged entity
  9940. // which might in turn cause _hasChanges to change.
  9941. if (!targetEntityState.isUnchanged) {
  9942. em._notifyStateChange(targetEntity, false);
  9943. }
  9944. } else {
  9945. updateEntityRef(queryContext, targetEntity, node);
  9946. // we still need to merge related entities even if top level entity wasn't modified.
  9947. entityType.navigationProperties.forEach(function (np) {
  9948. if (np.isScalar) {
  9949. mergeRelatedEntityCore(node, np, queryContext);
  9950. } else {
  9951. mergeRelatedEntitiesCore(node, np, queryContext);
  9952. }
  9953. });
  9954. }
  9955. } else {
  9956. targetEntity = entityType._createEntityCore();
  9957. if (targetEntity.initializeFrom) {
  9958. // allows any injected post ctor activity to be performed by modelLibrary impl.
  9959. targetEntity.initializeFrom(node);
  9960. }
  9961. updateEntity(targetEntity, node, queryContext);
  9962. targetEntity.entityAspect._postInitialize();
  9963. attachEntityCore(em, targetEntity, EntityState.Unchanged);
  9964. targetEntity.entityAspect.wasLoaded = true;
  9965. em.entityChanged.publish({ entityAction: EntityAction.AttachOnQuery, entity: targetEntity });
  9966. }
  9967. return targetEntity;
  9968. }
  9969. function processAnonType(node, queryContext) {
  9970. // node is guaranteed to be an object by this point, i.e. not a scalar
  9971. var em = queryContext.entityManager;
  9972. var jsonResultsAdapter = queryContext.jsonResultsAdapter;
  9973. var keyFn = em.metadataStore.namingConvention.serverPropertyNameToClient;
  9974. var result = { };
  9975. __objectForEach(node, function(key, value) {
  9976. var meta = jsonResultsAdapter.visitNode(value, queryContext, { nodeType: "anonProp", propertyName: key });
  9977. if (meta.ignore) return;
  9978. var newKey = keyFn(key);
  9979. if (Array.isArray(value)) {
  9980. result[newKey] = value.map(function(v, ix) {
  9981. meta = jsonResultsAdapter.visitNode(v, queryContext, { nodeType: "anonPropItem", propertyName: key });
  9982. return processMeta(v, queryContext, meta, function(refValue) {
  9983. result[newKey][ix] = refValue();
  9984. });
  9985. });
  9986. } else {
  9987. result[newKey] = processMeta(value, queryContext, meta, function(refValue) {
  9988. result[newKey] = refValue();
  9989. });
  9990. }
  9991. });
  9992. return result;
  9993. }
  9994. function updateEntity(targetEntity, rawEntity, queryContext) {
  9995. updateEntityRef(queryContext, targetEntity, rawEntity);
  9996. var entityType = targetEntity.entityType;
  9997. entityType.dataProperties.forEach(function (dp) {
  9998. if (dp.isUnmapped) return;
  9999. var val = rawEntity[dp.nameOnServer];
  10000. if (dp.dataType === DataType.DateTime && val) {
  10001. if (!__isDate(val)) {
  10002. val = DataType.parseDateFromServer(val);
  10003. }
  10004. } else if (dp.dataType == DataType.Binary) {
  10005. if (val && val.$value !== undefined) {
  10006. val = val.$value; // this will be a byte[] encoded as a string
  10007. }
  10008. } else if (dp.isComplexProperty) {
  10009. if (val != undefined) {
  10010. var coVal = targetEntity.getProperty(dp.name);
  10011. dp.dataType.dataProperties.forEach(function(cdp) {
  10012. // recursive call
  10013. coVal.setProperty(cdp.name, val[cdp.nameOnServer]);
  10014. });
  10015. }
  10016. }
  10017. if (!dp.isComplexProperty) {
  10018. targetEntity.setProperty(dp.name, val);
  10019. }
  10020. });
  10021. entityType.navigationProperties.forEach(function (np) {
  10022. if (np.isScalar) {
  10023. mergeRelatedEntity(np, targetEntity, rawEntity, queryContext);
  10024. } else {
  10025. mergeRelatedEntities(np, targetEntity, rawEntity, queryContext);
  10026. }
  10027. });
  10028. }
  10029. function updateEntityRef(queryContext, targetEntity, rawEntity) {
  10030. var nodeId = rawEntity._$meta.nodeId;
  10031. if (nodeId != null) {
  10032. queryContext.refMap[nodeId] = targetEntity;
  10033. }
  10034. }
  10035. function mergeRelatedEntity(navigationProperty, targetEntity, rawEntity, queryContext) {
  10036. var relatedEntity = mergeRelatedEntityCore(rawEntity, navigationProperty, queryContext);
  10037. if (relatedEntity == null) return;
  10038. if (typeof relatedEntity === 'function') {
  10039. queryContext.deferredFns.push(function() {
  10040. relatedEntity = relatedEntity();
  10041. updateRelatedEntity(relatedEntity, targetEntity, navigationProperty);
  10042. });
  10043. } else {
  10044. updateRelatedEntity(relatedEntity, targetEntity, navigationProperty);
  10045. }
  10046. }
  10047. function mergeRelatedEntityCore(rawEntity, navigationProperty, queryContext) {
  10048. var relatedRawEntity = rawEntity[navigationProperty.nameOnServer];
  10049. if (!relatedRawEntity) return null;
  10050. var relatedEntity = visitAndMerge(relatedRawEntity, queryContext, { nodeType: "navProp", navigationProperty: navigationProperty });
  10051. return relatedEntity;
  10052. }
  10053. function updateRelatedEntity(relatedEntity, targetEntity, navigationProperty) {
  10054. if (!relatedEntity) return;
  10055. var propName = navigationProperty.name;
  10056. var currentRelatedEntity = targetEntity.getProperty(propName);
  10057. // check if the related entity is already hooked up
  10058. if (currentRelatedEntity != relatedEntity) {
  10059. // if not hook up both directions.
  10060. targetEntity.setProperty(propName, relatedEntity);
  10061. var inverseProperty = navigationProperty.inverse;
  10062. if (!inverseProperty) return;
  10063. if (inverseProperty.isScalar) {
  10064. relatedEntity.setProperty(inverseProperty.name, targetEntity);
  10065. } else {
  10066. var collection = relatedEntity.getProperty(inverseProperty.name);
  10067. collection.push(targetEntity);
  10068. }
  10069. }
  10070. }
  10071. function mergeRelatedEntities(navigationProperty, targetEntity, rawEntity, queryContext) {
  10072. var relatedEntities = mergeRelatedEntitiesCore(rawEntity, navigationProperty, queryContext);
  10073. if (relatedEntities == null) return;
  10074. var inverseProperty = navigationProperty.inverse;
  10075. if (!inverseProperty) return;
  10076. var originalRelatedEntities = targetEntity.getProperty(navigationProperty.name);
  10077. originalRelatedEntities.wasLoaded = true;
  10078. relatedEntities.forEach(function (relatedEntity) {
  10079. if (typeof relatedEntity === 'function') {
  10080. queryContext.deferredFns.push(function() {
  10081. relatedEntity = relatedEntity();
  10082. updateRelatedEntityInCollection(relatedEntity, originalRelatedEntities, targetEntity, inverseProperty);
  10083. });
  10084. } else {
  10085. updateRelatedEntityInCollection(relatedEntity, originalRelatedEntities, targetEntity, inverseProperty);
  10086. }
  10087. });
  10088. }
  10089. function mergeRelatedEntitiesCore(rawEntity, navigationProperty, queryContext) {
  10090. var relatedRawEntities = rawEntity[navigationProperty.nameOnServer];
  10091. if (!relatedRawEntities) return null;
  10092. // needed if what is returned is not an array and we expect one - this happens with __deferred in OData.
  10093. if (!Array.isArray(relatedRawEntities)) return null;
  10094. var relatedEntities = relatedRawEntities.map(function(relatedRawEntity) {
  10095. return visitAndMerge(relatedRawEntity, queryContext, { nodeType: "navPropItem", navigationProperty: navigationProperty });
  10096. });
  10097. return relatedEntities;
  10098. }
  10099. function updateRelatedEntityInCollection(relatedEntity, relatedEntities, targetEntity, inverseProperty) {
  10100. if (!relatedEntity) return;
  10101. // check if the related entity is already hooked up
  10102. var thisEntity = relatedEntity.getProperty(inverseProperty.name);
  10103. if (thisEntity !== targetEntity) {
  10104. // if not - hook it up.
  10105. relatedEntities.push(relatedEntity);
  10106. relatedEntity.setProperty(inverseProperty.name, targetEntity);
  10107. }
  10108. }
  10109. function updateConcurrencyProperties(entities) {
  10110. var candidates = entities.filter(function (e) {
  10111. e.entityAspect.isBeingSaved = true;
  10112. return e.entityAspect.entityState.isModified()
  10113. && e.entityType.concurrencyProperties.length > 0;
  10114. });
  10115. if (candidates.length === 0) return;
  10116. candidates.forEach(function (c) {
  10117. c.entityType.concurrencyProperties.forEach(function (cp) {
  10118. updateConcurrencyProperty(c, cp);
  10119. });
  10120. });
  10121. }
  10122. function updateConcurrencyProperty(entity, property) {
  10123. // check if property has already been updated
  10124. if (entity.entityAspect.originalValues[property.name]) return;
  10125. var value = entity.getProperty(property.name);
  10126. if (!value) value = property.dataType.defaultValue;
  10127. if (property.dataType.isNumeric) {
  10128. entity.setProperty(property.name, value + 1);
  10129. } else if (property.dataType === DataType.DateTime) {
  10130. // use the current datetime but insure that it
  10131. // is different from previous call.
  10132. var dt = new Date();
  10133. var dt2 = new Date();
  10134. while (dt == dt2) {
  10135. dt2 = new Date();
  10136. }
  10137. entity.setProperty(property.name, dt2);
  10138. } else if (property.dataType === DataType.Guid) {
  10139. entity.setProperty(property.name, __getUuid());
  10140. } else if (property.dataType === DataType.Binary) {
  10141. // best guess - that this is a timestamp column and is computed on the server during save
  10142. // - so no need to set it here.
  10143. return;
  10144. } else {
  10145. // this just leaves DataTypes of Boolean, String and Byte - none of which should be the
  10146. // type for a concurrency column.
  10147. // NOTE: thought about just returning here but would rather be safe for now.
  10148. throw new Error("Unable to update the value of concurrency property before saving: " + property.name);
  10149. }
  10150. }
  10151. function toOdataQueryString(query, metadataStore) {
  10152. if (!query) {
  10153. throw new Error("query cannot be empty");
  10154. }
  10155. if (typeof query === 'string') {
  10156. return query;
  10157. } else if (query instanceof EntityQuery) {
  10158. return query._toUri(metadataStore);
  10159. } else {
  10160. throw new Error("unable to recognize query parameter as either a string or an EntityQuery");
  10161. }
  10162. }
  10163. function findOrCreateEntityGroup(em, entityType) {
  10164. var group = em._entityGroupMap[entityType.name];
  10165. if (!group) {
  10166. group = new EntityGroup(em, entityType);
  10167. em._entityGroupMap[entityType.name] = group;
  10168. }
  10169. return group;
  10170. }
  10171. function unwrapEntities(entities, metadataStore) {
  10172. var rawEntities = entities.map(function(e) {
  10173. var rawEntity = unwrapInstance(e);
  10174. var autoGeneratedKey = null;
  10175. if (e.entityType.autoGeneratedKeyType !== AutoGeneratedKeyType.None) {
  10176. autoGeneratedKey = {
  10177. propertyName: e.entityType.keyProperties[0].nameOnServer,
  10178. autoGeneratedKeyType: e.entityType.autoGeneratedKeyType.name
  10179. };
  10180. }
  10181. var originalValuesOnServer = unwrapOriginalValues(e, metadataStore);
  10182. rawEntity.entityAspect = {
  10183. entityTypeName: e.entityType.name,
  10184. entityState: e.entityAspect.entityState.name,
  10185. originalValuesMap: originalValuesOnServer,
  10186. autoGeneratedKey: autoGeneratedKey
  10187. };
  10188. return rawEntity;
  10189. });
  10190. return rawEntities;
  10191. }
  10192. function unwrapInstance(structObj) {
  10193. var rawObject = {};
  10194. var stype = structObj.entityType || structObj.complexType;
  10195. stype.dataProperties.forEach(function (dp) {
  10196. if (dp.isComplexProperty) {
  10197. rawObject[dp.nameOnServer] = unwrapInstance(structObj.getProperty(dp.name));
  10198. } else {
  10199. rawObject[dp.nameOnServer] = structObj.getProperty(dp.name);
  10200. }
  10201. });
  10202. return rawObject;
  10203. }
  10204. function unwrapOriginalValues(target, metadataStore) {
  10205. var stype = target.entityType || target.complexType;
  10206. var aspect = target.entityAspect || target.complexAspect;
  10207. var fn = metadataStore.namingConvention.clientPropertyNameToServer;
  10208. var result = {};
  10209. __objectForEach(aspect.originalValues, function (propName, value) {
  10210. var prop = stype.getProperty(propName);
  10211. result[fn(propName, prop)] = value;
  10212. });
  10213. stype.complexProperties.forEach(function(cp) {
  10214. var nextTarget = target.getProperty(cp.name);
  10215. var unwrappedCo = unwrapOriginalValues(nextTarget, metadataStore);
  10216. if (!__isEmpty(unwrappedCo)) {
  10217. result[fn(cp.name, cp)] = unwrappedCo;
  10218. }
  10219. });
  10220. return result;
  10221. }
  10222. function UnattachedChildrenMap() {
  10223. // key is EntityKey.toString(), value is array of { navigationProperty, children }
  10224. this.map = {};
  10225. }
  10226. UnattachedChildrenMap.prototype.addChild = function (parentEntityKey, navigationProperty, child) {
  10227. var tuple = this.getTuple(parentEntityKey, navigationProperty);
  10228. if (!tuple) {
  10229. var tuples = this.map[parentEntityKey.toString()];
  10230. if (!tuples) {
  10231. tuples = [];
  10232. this.map[parentEntityKey.toString()] = tuples;
  10233. }
  10234. tuple = { navigationProperty: navigationProperty, children: [] };
  10235. tuples.push(tuple);
  10236. }
  10237. tuple.children.push(child);
  10238. };
  10239. UnattachedChildrenMap.prototype.removeChildren = function (parentEntityKey, navigationProperty) {
  10240. var tuples = this.map[parentEntityKey.toString()];
  10241. if (!tuples) return;
  10242. __arrayRemoveItem(tuples, function (t) {
  10243. return t.navigationProperty === navigationProperty;
  10244. });
  10245. if (!tuples.length) {
  10246. delete this.map[parentEntityKey.toString()];
  10247. }
  10248. };
  10249. UnattachedChildrenMap.prototype.getChildren = function (parentEntityKey, navigationProperty) {
  10250. var tuple = this.getTuple(parentEntityKey, navigationProperty);
  10251. if (tuple) {
  10252. return tuple.children.filter(function (child) {
  10253. // it may have later been detached.
  10254. return !child.entityAspect.entityState.isDetached();
  10255. });
  10256. } else {
  10257. return null;
  10258. }
  10259. };
  10260. UnattachedChildrenMap.prototype.getTuple = function (parentEntityKey, navigationProperty) {
  10261. var tuples = this.map[parentEntityKey.toString()];
  10262. if (!tuples) return null;
  10263. var tuple = __arrayFirst(tuples, function (t) {
  10264. return t.navigationProperty === navigationProperty;
  10265. });
  10266. return tuple;
  10267. };
  10268. return ctor;
  10269. })();
  10270. var EntityGroup = (function () {
  10271. var __changedFilter = getFilter([EntityState.Added, EntityState.Modified, EntityState.Deleted]);
  10272. var ctor = function (entityManager, entityType) {
  10273. this.entityManager = entityManager;
  10274. this.entityType = entityType;
  10275. this._indexMap = {};
  10276. this._entities = [];
  10277. this._emptyIndexes = [];
  10278. };
  10279. var proto = ctor.prototype;
  10280. proto.attachEntity = function (entity, entityState) {
  10281. // entity should already have an aspect.
  10282. var ix;
  10283. var aspect = entity.entityAspect;
  10284. var keyInGroup = aspect.getKey()._keyInGroup;
  10285. ix = this._indexMap[keyInGroup];
  10286. if (ix >= 0) {
  10287. if (this._entities[ix] === entity) {
  10288. return entity;
  10289. }
  10290. throw new Error("This key is already attached: " + aspect.getKey());
  10291. }
  10292. if (this._emptyIndexes.length === 0) {
  10293. ix = this._entities.push(entity) - 1;
  10294. } else {
  10295. ix = this._emptyIndexes.pop();
  10296. this._entities[ix] = entity;
  10297. }
  10298. this._indexMap[keyInGroup] = ix;
  10299. aspect.entityState = entityState;
  10300. aspect.entityGroup = this;
  10301. aspect.entityManager = this.entityManager;
  10302. return entity;
  10303. };
  10304. proto.detachEntity = function (entity) {
  10305. // by this point we have already determined that this entity
  10306. // belongs to this group.
  10307. var aspect = entity.entityAspect;
  10308. var keyInGroup = aspect.getKey()._keyInGroup;
  10309. var ix = this._indexMap[keyInGroup];
  10310. if (ix === undefined) {
  10311. // shouldn't happen.
  10312. throw new Error("internal error - entity cannot be found in group");
  10313. }
  10314. delete this._indexMap[keyInGroup];
  10315. this._emptyIndexes.push(ix);
  10316. this._entities[ix] = null;
  10317. return entity;
  10318. };
  10319. // returns entity based on an entity key defined either as an array of key values or an EntityKey
  10320. proto.findEntityByKey = function (entityKey) {
  10321. var keyInGroup;
  10322. if (entityKey instanceof EntityKey) {
  10323. keyInGroup = entityKey._keyInGroup;
  10324. } else {
  10325. keyInGroup = EntityKey.createKeyString(entityKey);
  10326. }
  10327. var ix = this._indexMap[keyInGroup];
  10328. // can't use just (ix) below because 0 is valid
  10329. return (ix !== undefined) ? this._entities[ix] : null;
  10330. };
  10331. proto.hasChanges = function() {
  10332. return this._entities.some(__changedFilter);
  10333. };
  10334. proto.getEntities = function (entityStates) {
  10335. var filter = getFilter(entityStates);
  10336. var changes = this._entities.filter(filter);
  10337. return changes;
  10338. };
  10339. // do not expose this method. It is doing a special purpose INCOMPLETE fast detach operation
  10340. // just for the entityManager clear method - the entityGroup will be in an inconsistent state
  10341. // after this op, which is ok because it will be thrown away.
  10342. proto._clear = function() {
  10343. this._entities.forEach(function (entity) {
  10344. if (entity != null) {
  10345. entity.entityAspect._detach();
  10346. }
  10347. });
  10348. this._entities = null;
  10349. this._indexMap = null;
  10350. this._emptyIndexes = null;
  10351. };
  10352. proto._fixupKey = function (tempValue, realValue) {
  10353. // single part keys appear directly in map
  10354. var ix = this._indexMap[tempValue];
  10355. if (ix === undefined) {
  10356. throw new Error("Internal Error in key fixup - unable to locate entity");
  10357. }
  10358. var entity = this._entities[ix];
  10359. var keyPropName = entity.entityType.keyProperties[0].name;
  10360. // fks on related entities will automatically get updated by this as well
  10361. entity.setProperty(keyPropName, realValue);
  10362. delete entity.entityAspect.hasTempKey;
  10363. delete this._indexMap[tempValue];
  10364. this._indexMap[realValue] = ix;
  10365. };
  10366. proto._replaceKey = function(oldKey, newKey) {
  10367. var ix = this._indexMap[oldKey._keyInGroup];
  10368. delete this._indexMap[oldKey._keyInGroup];
  10369. this._indexMap[newKey._keyInGroup] = ix;
  10370. };
  10371. function getFilter(entityStates) {
  10372. if (!entityStates) {
  10373. return function (e) {
  10374. return !!e;
  10375. };
  10376. } else if (entityStates.length === 1) {
  10377. var entityState = entityStates[0];
  10378. return function (e) {
  10379. if (!e) return false;
  10380. return e.entityAspect.entityState === entityState;
  10381. };
  10382. } else {
  10383. return function (e) {
  10384. if (!e) return false;
  10385. return entityStates.some(function (es) {
  10386. return e.entityAspect.entityState === es;
  10387. });
  10388. };
  10389. }
  10390. }
  10391. return ctor;
  10392. })();
  10393. // expose
  10394. breeze.EntityManager = EntityManager;
  10395. /**
  10396. @module breeze
  10397. **/
  10398. var MergeStrategy = (function() {
  10399. /**
  10400. MergeStrategy is an 'Enum' that determines how entities are merged into an EntityManager.
  10401. @class MergeStrategy
  10402. @static
  10403. **/
  10404. var MergeStrategy = new Enum("MergeStrategy");
  10405. /**
  10406. PreserveChanges is used to stop merging from occuring if the existing entity in an entityManager is already
  10407. in a {{#crossLink "EntityState/Modified"}}{{/crossLink}} state. In this case, the existing entity in the
  10408. EntityManager is not replaced by the 'merging' entity.
  10409. @property PreserveChanges {MergeStrategy}
  10410. @final
  10411. @static
  10412. **/
  10413. MergeStrategy.PreserveChanges = MergeStrategy.addSymbol();
  10414. /**
  10415. OverwriteChanges is used to allow merging to occur even if the existing entity in an entityManager is already
  10416. in a {{#crossLink "EntityState/Modified"}}{{/crossLink}} state. In this case, the existing entity in the
  10417. EntityManager is replaced by the 'merging' entity.
  10418. @property OverwriteChanges {MergeStrategy}
  10419. @final
  10420. @static
  10421. **/
  10422. MergeStrategy.OverwriteChanges = MergeStrategy.addSymbol();
  10423. MergeStrategy.seal();
  10424. return MergeStrategy;
  10425. })();
  10426. var FetchStrategy = (function() {
  10427. /**
  10428. FetchStrategy is an 'Enum' that determines how and where entities are retrieved from as a result of a query.
  10429. @class FetchStrategy
  10430. @static
  10431. **/
  10432. var FetchStrategy = new Enum("FetchStrategy");
  10433. /**
  10434. FromServer is used to tell the query to execute the query against a remote data source on the server.
  10435. @property FromServer {MergeStrategy}
  10436. @final
  10437. @static
  10438. **/
  10439. FetchStrategy.FromServer = FetchStrategy.addSymbol();
  10440. /**
  10441. FromLocalCache is used to tell the query to execute the query against a local EntityManager instead of going to a remote server.
  10442. @property FromLocalCache {MergeStrategy}
  10443. @final
  10444. @static
  10445. **/
  10446. FetchStrategy.FromLocalCache = FetchStrategy.addSymbol();
  10447. FetchStrategy.seal();
  10448. return FetchStrategy;
  10449. })();
  10450. var QueryOptions = (function () {
  10451. /**
  10452. A QueryOptions instance is used to specify the 'options' under which a query will occur.
  10453. @class QueryOptions
  10454. **/
  10455. /**
  10456. QueryOptions constructor
  10457. @example
  10458. var newQo = new QueryOptions( { mergeStrategy: MergeStrategy.OverwriteChanges });
  10459. // assume em1 is a preexisting EntityManager
  10460. em1.setProperties( { queryOptions: newQo });
  10461. @method <ctor> QueryOptions
  10462. @param [config] {Object}
  10463. @param [config.fetchStrategy=FetchStrategy.FromServer] {FetchStrategy}
  10464. @param [config.mergeStrategy=MergeStrategy.PreserveChanges] {MergeStrategy}
  10465. **/
  10466. var ctor = function (config) {
  10467. this.fetchStrategy = FetchStrategy.FromServer;
  10468. this.mergeStrategy = MergeStrategy.PreserveChanges;
  10469. updateWithConfig(this, config);
  10470. };
  10471. var proto = ctor.prototype;
  10472. /**
  10473. A {{#crossLink "FetchStrategy"}}{{/crossLink}}
  10474. __readOnly__
  10475. @property fetchStrategy {FetchStrategy}
  10476. **/
  10477. /**
  10478. A {{#crossLink "MergeStrategy"}}{{/crossLink}}
  10479. __readOnly__
  10480. @property mergeStrategy {MergeStrategy}
  10481. **/
  10482. proto._$typeName = "QueryOptions";
  10483. /**
  10484. The default value whenever QueryOptions are not specified.
  10485. @property defaultInstance {QueryOptions}
  10486. @static
  10487. **/
  10488. ctor.defaultInstance = new ctor();
  10489. /**
  10490. Returns a copy of this QueryOptions with the specified {{#crossLink "MergeStrategy"}}{{/crossLink}}
  10491. or {{#crossLink "FetchStrategy"}}{{/crossLink}} applied.
  10492. @example
  10493. var queryOptions = em1.queryOptions.using(MergeStrategy.PreserveChanges);
  10494. or
  10495. @example
  10496. var queryOptions = em1.queryOptions.using(FetchStrategy.FromLocalCache);
  10497. or
  10498. @example
  10499. var queryOptions = em1.queryOptions.using( { mergeStrategy: OverwriteChanges });
  10500. @method using
  10501. @param config {Configuration Object|MergeStrategy|FetchStrategy} The object to apply to create a new QueryOptions.
  10502. @return {QueryOptions}
  10503. @chainable
  10504. **/
  10505. proto.using = function(config) {
  10506. var result = new QueryOptions(this);
  10507. if (MergeStrategy.contains(config)) {
  10508. config = { mergeStrategy: config };
  10509. } else if (FetchStrategy.contains(config)) {
  10510. config = { fetchStrategy: config };
  10511. }
  10512. return updateWithConfig(result, config);
  10513. };
  10514. /**
  10515. Makes this instance the default instance.
  10516. @method setAsDefault
  10517. @example
  10518. var newQo = new QueryOptions( { mergeStrategy: MergeStrategy.OverwriteChanges });
  10519. newQo.setAsDefault();
  10520. @chainable
  10521. **/
  10522. proto.setAsDefault = function() {
  10523. ctor.defaultInstance = this;
  10524. return this;
  10525. };
  10526. proto.toJSON = function () {
  10527. return __toJson(this);
  10528. };
  10529. ctor.fromJSON = function (json) {
  10530. return new QueryOptions({
  10531. fetchStrategy: FetchStrategy.fromName(json.fetchStrategy),
  10532. mergeStrategy: MergeStrategy.fromName(json.mergeStrategy)
  10533. });
  10534. };
  10535. function updateWithConfig( obj, config ) {
  10536. if (config) {
  10537. assertConfig(config)
  10538. .whereParam("fetchStrategy").isEnumOf(FetchStrategy).isOptional()
  10539. .whereParam("mergeStrategy").isEnumOf(MergeStrategy).isOptional()
  10540. .applyAll(obj);
  10541. }
  10542. return obj;
  10543. }
  10544. return ctor;
  10545. })();
  10546. var SaveOptions = (function () {
  10547. /**
  10548. A SaveOptions instance is used to specify the 'options' under which a save will occur.
  10549. @class SaveOptions
  10550. **/
  10551. /**
  10552. @method <ctor> SaveOptions
  10553. @param config {Object}
  10554. @param [config.allowConcurrentSaves] {Boolean}
  10555. @param [config.tag] {Object} Free form value that will be sent to the server.
  10556. **/
  10557. var ctor = function (config) {
  10558. config = config || {};
  10559. assertConfig(config)
  10560. .whereParam("allowConcurrentSaves").isBoolean().isOptional().withDefault(false)
  10561. .whereParam("tag").isOptional()
  10562. .applyAll(this);
  10563. };
  10564. var proto = ctor.prototype;
  10565. proto._$typeName = "SaveOptions";
  10566. /**
  10567. Makes this instance the default instance.
  10568. @method setAsDefault
  10569. @chainable
  10570. **/
  10571. proto.setAsDefault = function() {
  10572. ctor.defaultInstance = this;
  10573. return this;
  10574. };
  10575. /**
  10576. Whether another save can be occuring at the same time as this one - default is false.
  10577. __readOnly__
  10578. @property allowConcurrentSaves {Boolean}
  10579. **/
  10580. /**
  10581. A free form value that will be sent to the server.
  10582. __readOnly__
  10583. @property tag {Object}
  10584. **/
  10585. /**
  10586. The default value whenever SaveOptions are not specified.
  10587. @property defaultInstance {SaveOptions}
  10588. @static
  10589. **/
  10590. ctor.defaultInstance = new ctor();
  10591. return ctor;
  10592. })();
  10593. var ValidationOptions = (function () {
  10594. /**
  10595. A ValidationOptions instance is used to specify the conditions under which validation will be executed.
  10596. @class ValidationOptions
  10597. **/
  10598. /**
  10599. ValidationOptions constructor
  10600. @example
  10601. var newVo = new ValidationOptions( { validateOnSave: false, validateOnAttach: false });
  10602. // assume em1 is a preexisting EntityManager
  10603. em1.setProperties( { validationOptions: newVo });
  10604. @method <ctor> ValidationOptions
  10605. @param [config] {Object}
  10606. @param [config.validateOnAttach=true] {Boolean}
  10607. @param [config.validateOnSave=true] {Boolean}
  10608. @param [config.validateOnQuery=false] {Boolean}
  10609. @param [config.validateOnPropertyChange=true] {Boolean}
  10610. **/
  10611. var ctor = function (config) {
  10612. this.validateOnAttach = true;
  10613. this.validateOnSave = true;
  10614. this.validateOnQuery = false;
  10615. this.validateOnPropertyChange = true;
  10616. updateWithConfig(this, config);
  10617. };
  10618. var proto = ctor.prototype;
  10619. /**
  10620. Whether entity and property level validation should occur when entities are attached to the EntityManager other than via a query.
  10621. __readOnly__
  10622. @property validateOnAttach {Boolean}
  10623. **/
  10624. /**
  10625. Whether entity and property level validation should occur before entities are saved. A failed validation will force the save to fail early.
  10626. __readOnly__
  10627. @property validateOnSave {Boolean}
  10628. **/
  10629. /**
  10630. Whether entity and property level validation should occur after entities are queried from a remote server.
  10631. __readOnly__
  10632. @property validateOnQuery {Boolean}
  10633. **/
  10634. /**
  10635. Whether property level validation should occur after entities are modified.
  10636. __readOnly__
  10637. @property validateOnPropertyChange {Boolean}
  10638. **/
  10639. proto._$typeName = "ValidationOptions";
  10640. /**
  10641. Returns a copy of this ValidationOptions with changes to the specified config properties.
  10642. @example
  10643. var validationOptions = new ValidationOptions();
  10644. var newOptions = validationOptions.using( { validateOnQuery: true, validateOnSave: false} );
  10645. @method using
  10646. @param config {Object} The object to apply to create a new QueryOptions.
  10647. @param [config.validateOnAttach] {Boolean}
  10648. @param [config.validateOnSave] {Boolean}
  10649. @param [config.validateOnQuery] {Boolean}
  10650. @param [config.validateOnPropertyChange] {Boolean}
  10651. @return {ValidationOptions}
  10652. @chainable
  10653. **/
  10654. proto.using = function(config) {
  10655. var result = new ValidationOptions(this);
  10656. updateWithConfig(result, config);
  10657. return result;
  10658. };
  10659. /**
  10660. Makes this instance the default instance.
  10661. @example
  10662. var validationOptions = new ValidationOptions()
  10663. var newOptions = validationOptions.using( { validateOnQuery: true, validateOnSave: false} );
  10664. var newOptions.setAsDefault();
  10665. @method setAsDefault
  10666. @chainable
  10667. **/
  10668. proto.setAsDefault = function() {
  10669. ctor.defaultInstance = this;
  10670. return this;
  10671. };
  10672. /**
  10673. The default value whenever ValidationOptions are not specified.
  10674. @property defaultInstance {ValidationOptions}
  10675. @static
  10676. **/
  10677. ctor.defaultInstance = new ctor();
  10678. function updateWithConfig( obj, config ) {
  10679. if (config) {
  10680. assertConfig(config)
  10681. .whereParam("validateOnAttach").isBoolean().isOptional()
  10682. .whereParam("validateOnSave").isBoolean().isOptional()
  10683. .whereParam("validateOnQuery").isBoolean().isOptional()
  10684. .whereParam("validateOnPropertyChange").isBoolean().isOptional()
  10685. .applyAll(obj);
  10686. }
  10687. return obj;
  10688. }
  10689. return ctor;
  10690. })();
  10691. // expose
  10692. breeze.QueryOptions= QueryOptions;
  10693. breeze.SaveOptions= SaveOptions;
  10694. breeze.ValidationOptions = ValidationOptions;
  10695. breeze.FetchStrategy= FetchStrategy;
  10696. breeze.MergeStrategy = MergeStrategy;
  10697. // needs JQuery
  10698. (function(factory) {
  10699. // Module systems magic dance.
  10700. if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
  10701. // CommonJS or Node: hard-coded dependency on "breeze"
  10702. factory(require("breeze"));
  10703. } else if (typeof define === "function" && define["amd"]) {
  10704. // AMD anonymous module with hard-coded dependency on "breeze"
  10705. if (breeze) {
  10706. factory(breeze);
  10707. } else {
  10708. define(["breeze"], factory);
  10709. }
  10710. } else {
  10711. // <script> tag: use the global `breeze` object
  10712. factory(breeze);
  10713. }
  10714. }(function(breeze) {
  10715. var core = breeze.core;
  10716. var jQuery;
  10717. var ctor = function () {
  10718. this.name = "jQuery";
  10719. this.defaultSettings = { };
  10720. };
  10721. ctor.prototype.initialize = function () {
  10722. jQuery = core.requireLib("jQuery", "needed for 'ajax_jQuery' pluggin");
  10723. };
  10724. ctor.prototype.ajax = function (settings) {
  10725. if (! core.isEmpty(this.defaultSettings)) {
  10726. var compositeSettings = core.extend({}, this.defaultSettings);
  10727. core.extend(compositeSettings, settings);
  10728. jQuery.ajax(compositeSettings);
  10729. } else {
  10730. jQuery.ajax(settings);
  10731. }
  10732. };
  10733. // last param is true because for now we only have one impl.
  10734. breeze.config.registerAdapter("ajax", ctor);
  10735. }));
  10736. (function (factory) {
  10737. if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
  10738. // CommonJS or Node: hard-coded dependency on "breeze"
  10739. factory(require("breeze"));
  10740. } else if (typeof define === "function" && define["amd"] && !breeze) {
  10741. // AMD anonymous module with hard-coded dependency on "breeze"
  10742. define(["breeze"], factory);
  10743. } else {
  10744. // <script> tag: use the global `breeze` object
  10745. factory(breeze);
  10746. }
  10747. }(function(breeze) {
  10748. var core = breeze.core;
  10749. var EntityType = breeze.EntityType;
  10750. var JsonResultsAdapter = breeze.JsonResultsAdapter;
  10751. var OData;
  10752. var ctor = function () {
  10753. this.name = "OData";
  10754. };
  10755. ctor.prototype.initialize = function () {
  10756. OData = core.requireLib("OData", "Needed to support remote OData services");
  10757. OData.jsonHandler.recognizeDates = true;
  10758. };
  10759. ctor.prototype.executeQuery = function (entityManager, odataQuery, collectionCallback, errorCallback) {
  10760. var url = entityManager.serviceName + odataQuery;
  10761. OData.read(url,
  10762. function (data, response) {
  10763. collectionCallback({ results: data.results, inlineCount: data.__count });
  10764. },
  10765. function (error) {
  10766. if (errorCallback) errorCallback(createError(error));
  10767. }
  10768. );
  10769. };
  10770. ctor.prototype.fetchMetadata = function (metadataStore, dataService, callback, errorCallback) {
  10771. var serviceName = dataService.serviceName;
  10772. var metadataSvcUrl = getMetadataUrl(serviceName);
  10773. OData.read(metadataSvcUrl,
  10774. function (data) {
  10775. // data.dataServices.schema is an array of schemas. with properties of
  10776. // entityContainer[], association[], entityType[], and namespace.
  10777. if (!data || !data.dataServices) {
  10778. var error = new Error("Metadata query failed for: " + metadataSvcUrl);
  10779. if (onError) {
  10780. onError(error);
  10781. } else {
  10782. callback(error);
  10783. }
  10784. }
  10785. var schema = data.dataServices.schema;
  10786. // might have been fetched by another query
  10787. if (!metadataStore.hasMetadataFor(serviceName)) {
  10788. metadataStore._parseODataMetadata(serviceName, schema);
  10789. metadataStore.addDataService(dataService);
  10790. }
  10791. if (callback) {
  10792. callback(schema);
  10793. }
  10794. }, function (error) {
  10795. var err = createError(error);
  10796. err.message = "Metadata query failed for: " + metadataSvcUrl + "; " + (err.message || "");
  10797. if (errorCallback) errorCallback(err);
  10798. },
  10799. OData.metadataHandler
  10800. );
  10801. };
  10802. ctor.prototype.saveChanges = function (entityManager, saveBundleStringified, callback, errorCallback) {
  10803. throw new Error("Breeze does not yet support saving thru OData");
  10804. };
  10805. ctor.prototype.jsonResultsAdapter = new JsonResultsAdapter({
  10806. name: "OData_default",
  10807. visitNode: function (node, queryContext, nodeContext) {
  10808. var result = {};
  10809. if (node.__metadata != null) {
  10810. // TODO: may be able to make this more efficient by caching of the previous value.
  10811. var entityTypeName = EntityType._getNormalizedTypeName(node.__metadata.type);
  10812. var et = entityTypeName && queryContext.entityManager.metadataStore.getEntityType(entityTypeName, true);
  10813. if (et && et._mappedPropertiesCount === Object.keys(node).length - 1) {
  10814. result.entityType = et;
  10815. }
  10816. }
  10817. var propertyName = nodeContext.propertyName;
  10818. result.ignore = node.__deferred != null || propertyName == "__metadata" ||
  10819. // EntityKey properties can be produced by EDMX models
  10820. (propertyName == "EntityKey" && node.$type && core.stringStartsWith(node.$type, "System.Data"));
  10821. return result;
  10822. },
  10823. });
  10824. function getMetadataUrl(serviceName) {
  10825. var metadataSvcUrl = serviceName;
  10826. // remove any trailing "/"
  10827. if (core.stringEndsWith(metadataSvcUrl, "/")) {
  10828. metadataSvcUrl = metadataSvcUrl.substr(0, metadataSvcUrl.length - 1);
  10829. }
  10830. // ensure that it ends with /$metadata
  10831. if (!core.stringEndsWith(metadataSvcUrl, "/$metadata")) {
  10832. metadataSvcUrl = metadataSvcUrl + "/$metadata";
  10833. }
  10834. return metadataSvcUrl;
  10835. };
  10836. function createError(error) {
  10837. var err = new Error();
  10838. var response = error.response;
  10839. err.message = response.statusText;
  10840. err.statusText = response.statusText;
  10841. err.status = response.statusCode;
  10842. // non std
  10843. err.body = response.body;
  10844. err.requestUri = response.requestUri;
  10845. if (response.body) {
  10846. try {
  10847. var responseObj = JSON.parse(response.body);
  10848. err.detail = responseObj;
  10849. err.message = responseObj.error.message.value;
  10850. } catch (e) {
  10851. }
  10852. }
  10853. return err;
  10854. }
  10855. breeze.config.registerAdapter("dataService", ctor);
  10856. }));
  10857. (function(factory) {
  10858. if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
  10859. // CommonJS or Node: hard-coded dependency on "breeze"
  10860. factory(require("breeze"));
  10861. } else if (typeof define === "function" && define["amd"] && !breeze) {
  10862. // AMD anonymous module with hard-coded dependency on "breeze"
  10863. define(["breeze"], factory);
  10864. } else {
  10865. // <script> tag: use the global `breeze` object
  10866. factory(breeze);
  10867. }
  10868. }(function(breeze) {
  10869. var core = breeze.core;
  10870. var EntityType = breeze.EntityType;
  10871. var JsonResultsAdapter = breeze.JsonResultsAdapter;
  10872. var ajaxImpl;
  10873. var ctor = function () {
  10874. this.name = "webApi";
  10875. };
  10876. ctor.prototype.checkForRecomposition = function (interfaceInitializedArgs) {
  10877. if (interfaceInitializedArgs.interfaceName === "ajax" && interfaceInitializedArgs.isDefault) {
  10878. this.initialize();
  10879. }
  10880. };
  10881. ctor.prototype.initialize = function () {
  10882. ajaxImpl = breeze.config.getAdapterInstance("ajax");
  10883. if (!ajaxImpl) {
  10884. throw new Error("Unable to initialize ajax for WebApi.");
  10885. }
  10886. // don't cache 'ajax' because we then we would need to ".bind" it, and don't want to because of brower support issues.
  10887. var ajax = ajaxImpl.ajax;
  10888. if (!ajax) {
  10889. throw new Error("Breeze was unable to find an 'ajax' adapter");
  10890. }
  10891. };
  10892. ctor.prototype.fetchMetadata = function (metadataStore, dataService, callback, errorCallback) {
  10893. var serviceName = dataService.serviceName;
  10894. var metadataSvcUrl = getMetadataUrl(serviceName);
  10895. ajaxImpl.ajax({
  10896. url: metadataSvcUrl,
  10897. dataType: 'json',
  10898. success: function(data, textStatus, XHR) {
  10899. var metadata = JSON.parse(data);
  10900. if (!metadata) {
  10901. if (errorCallback) errorCallback(new Error("Metadata query failed for: " + metadataSvcUrl));
  10902. return;
  10903. }
  10904. var schema = metadata.schema; // || metadata.dataServices.schema; ... for ODataModelBuilder maybe later
  10905. if (!schema) {
  10906. if (errorCallback) errorCallback(new Error("Metadata query failed for " + metadataSvcUrl + "; Unable to locate 'schema' member in metadata"));
  10907. return;
  10908. }
  10909. // might have been fetched by another query
  10910. if (!metadataStore.hasMetadataFor(serviceName)) {
  10911. metadataStore._parseODataMetadata(serviceName, schema);
  10912. metadataStore.addDataService(dataService);
  10913. }
  10914. if (callback) {
  10915. callback(schema);
  10916. }
  10917. XHR.onreadystatechange = null;
  10918. XHR.abort = null;
  10919. },
  10920. error: function (XHR, textStatus, errorThrown) {
  10921. handleXHRError(XHR, errorCallback, "Metadata query failed for: " + metadataSvcUrl);
  10922. }
  10923. });
  10924. };
  10925. ctor.prototype.executeQuery = function (entityManager, odataQuery, collectionCallback, errorCallback) {
  10926. var url = entityManager.serviceName + odataQuery;
  10927. ajaxImpl.ajax({
  10928. url: url,
  10929. dataType: 'json',
  10930. success: function(data, textStatus, XHR) {
  10931. // jQuery.getJSON(url).done(function (data, textStatus, jqXHR) {
  10932. try {
  10933. var inlineCount = XHR.getResponseHeader("X-InlineCount");
  10934. if (inlineCount) {
  10935. inlineCount = parseInt(inlineCount, 10);
  10936. }
  10937. collectionCallback({ results: data, XHR: XHR, inlineCount: inlineCount });
  10938. XHR.onreadystatechange = null;
  10939. XHR.abort = null;
  10940. } catch (e) {
  10941. var error = e instanceof Error ? e : createError(XHR);
  10942. // needed because it doesn't look like jquery calls .fail if an error occurs within the function
  10943. if (errorCallback) errorCallback(error);
  10944. XHR.onreadystatechange = null;
  10945. XHR.abort = null;
  10946. }
  10947. },
  10948. error: function (XHR, textStatus, errorThrown) {
  10949. handleXHRError(XHR, errorCallback);
  10950. }
  10951. });
  10952. };
  10953. ctor.prototype.saveChanges = function (entityManager, saveBundleStringified, callback, errorCallback) {
  10954. var url = entityManager.serviceName + "SaveChanges";
  10955. ajaxImpl.ajax({
  10956. url: url,
  10957. type: "POST",
  10958. dataType: 'json',
  10959. contentType: "application/json",
  10960. data: saveBundleStringified,
  10961. success: function(data, textStatus, XHR) {
  10962. if (data.Error) {
  10963. // anticipatable errors on server - concurrency...
  10964. var err = createError(XHR);
  10965. err.message = data.Error;
  10966. errorCallback(err);
  10967. } else {
  10968. data.XHR = XHR;
  10969. callback(data);
  10970. }
  10971. },
  10972. error: function (XHR, textStatus, errorThrown) {
  10973. handleXHRError(XHR, errorCallback);
  10974. }
  10975. });
  10976. };
  10977. ctor.prototype.jsonResultsAdapter = new JsonResultsAdapter({
  10978. name: "webApi_default",
  10979. visitNode: function (node, queryContext, nodeContext ) {
  10980. var entityTypeName = EntityType._getNormalizedTypeName(node.$type);
  10981. var entityType = entityTypeName && queryContext.entityManager.metadataStore.getEntityType(entityTypeName, true);
  10982. var propertyName = nodeContext.propertyName;
  10983. var ignore = propertyName && propertyName.substr(0, 1) === "$";
  10984. return {
  10985. entityType: entityType,
  10986. nodeId: node.$id,
  10987. nodeRefId: node.$ref,
  10988. ignore: ignore
  10989. };
  10990. },
  10991. });
  10992. function getMetadataUrl(serviceName) {
  10993. var metadataSvcUrl = serviceName;
  10994. // remove any trailing "/"
  10995. if (core.stringEndsWith(metadataSvcUrl, "/")) {
  10996. metadataSvcUrl = metadataSvcUrl.substr(0, metadataSvcUrl.length - 1);
  10997. }
  10998. // ensure that it ends with /Metadata
  10999. if (!core.stringEndsWith(metadataSvcUrl, "/Metadata")) {
  11000. metadataSvcUrl = metadataSvcUrl + "/Metadata";
  11001. }
  11002. return metadataSvcUrl;
  11003. }
  11004. function handleXHRError(XHR, errorCallback, messagePrefix) {
  11005. if (!errorCallback) return;
  11006. var err = createError(XHR);
  11007. if (messagePrefix) {
  11008. err.message = messagePrefix + "; " + +err.message;
  11009. }
  11010. errorCallback(err);
  11011. XHR.onreadystatechange = null;
  11012. XHR.abort = null;
  11013. }
  11014. function createError(XHR) {
  11015. var err = new Error();
  11016. err.XHR = XHR;
  11017. err.message = XHR.statusText;
  11018. err.responseText = XHR.responseText;
  11019. err.status = XHR.status;
  11020. err.statusText = XHR.statusText;
  11021. if (err.responseText) {
  11022. try {
  11023. var responseObj = JSON.parse(XHR.responseText);
  11024. err.detail = responseObj;
  11025. var source = responseObj.InnerException || responseObj;
  11026. err.message = source.ExceptionMessage || source.Message || XHR.responseText;
  11027. } catch (e) {
  11028. }
  11029. }
  11030. return err;
  11031. }
  11032. breeze.config.registerAdapter("dataService", ctor);
  11033. }));
  11034. "use strict";
  11035. (function (factory) {
  11036. if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
  11037. // CommonJS or Node: hard-coded dependency on "breeze"
  11038. factory(require("breeze"));
  11039. } else if (typeof define === "function" && define["amd"] && !breeze) {
  11040. // AMD anonymous module with hard-coded dependency on "breeze"
  11041. define(["breeze"], factory);
  11042. } else {
  11043. // <script> tag: use the global `breeze` object
  11044. factory(breeze);
  11045. }
  11046. }(function(breeze) {
  11047. var core = breeze.core;
  11048. var ComplexAspect = breeze.ComplexAspect;
  11049. var Backbone;
  11050. var _;
  11051. var bbSet, bbGet;
  11052. var hasOwnProperty = Object.prototype.hasOwnProperty;
  11053. var ctor = function () {
  11054. this.name = "backbone";
  11055. };
  11056. ctor.prototype.initialize = function() {
  11057. Backbone = core.requireLib("Backbone");
  11058. _ = core.requireLib("_;underscore");
  11059. bbSet = Backbone.Model.prototype.set;
  11060. bbGet = Backbone.Model.prototype.get;
  11061. };
  11062. // may be an entityType or a complexType
  11063. ctor.prototype.createCtor = function(structuralType) {
  11064. var defaults = { };
  11065. structuralType.dataProperties.forEach(function (dp) {
  11066. defaults[dp.name] = dp.defaultValue;
  11067. });
  11068. var modelCtor = Backbone.Model.extend({
  11069. defaults: defaults,
  11070. initialize: function () {
  11071. if (structuralType.navigationProperties) {
  11072. var that = this;
  11073. structuralType.navigationProperties.forEach(function (np) {
  11074. if (!np.isScalar) {
  11075. var val = breeze.makeRelationArray([], that, np);
  11076. Backbone.Model.prototype.set.call(that, np.name, val);
  11077. }
  11078. });
  11079. }
  11080. }
  11081. });
  11082. return modelCtor;
  11083. };
  11084. ctor.prototype.getTrackablePropertyNames = function(entity) {
  11085. var names = [];
  11086. for (var p in entity.attributes) {
  11087. names.push(p);
  11088. }
  11089. return names;
  11090. };
  11091. ctor.prototype.initializeEntityPrototype = function(proto) {
  11092. proto.getProperty = function(propertyName) {
  11093. return this.get(propertyName);
  11094. };
  11095. proto.setProperty = function(propertyName, value) {
  11096. this.set(propertyName, value);
  11097. // allow setProperty chaining.
  11098. return this;
  11099. };
  11100. // override Backbone's set method.
  11101. proto.set = function(key, value, options) {
  11102. // call Backbone validate first - we need this because if it fails we don't want to call the Breeze interceptor.
  11103. // if valid then call Breeze interceptor which will call Backbone's internal set
  11104. var aspect = this.entityAspect || this.complexAspect;
  11105. if (!aspect) {
  11106. return bbSet.call(this, key, value, options);
  11107. }
  11108. var attrs, prop, propName;
  11109. var that = this;
  11110. var stype = this.entityType || this.complexType;
  11111. // Handle both `"key", value` and `{key: value}` -style arguments.
  11112. if (_.isObject(key) || key == null) {
  11113. attrs = key;
  11114. options = value;
  11115. if (!this._validate(attrs, options)) return false;
  11116. // TODO: suppress validate here
  11117. for (propName in attrs) {
  11118. if (hasOwnProperty.call(attrs, propName)) {
  11119. prop = stype.getProperty(propName);
  11120. if (prop == null) {
  11121. throw new Error("Unknown property: " + key);
  11122. }
  11123. this._$interceptor(prop, attrs[propName], function(pvalue) {
  11124. if (arguments.length === 0) {
  11125. return bbGet.call(that, propName);
  11126. } else {
  11127. return bbSet.call(that, propName, pvalue, options);
  11128. }
  11129. });
  11130. }
  11131. }
  11132. } else {
  11133. attrs = { };
  11134. attrs[key] = value;
  11135. options || (options = { });
  11136. if (!this._validate(attrs, options)) return false;
  11137. // TODO: suppress validate here
  11138. prop = stype.getProperty(key);
  11139. if (prop == null) {
  11140. throw new Error("Unknown property: " + key);
  11141. }
  11142. propName = key;
  11143. this._$interceptor(prop, value, function(pvalue) {
  11144. if (arguments.length === 0) {
  11145. return bbGet.call(that, propName);
  11146. } else {
  11147. return bbSet.call(that, propName, pvalue, options);
  11148. }
  11149. });
  11150. }
  11151. return this;
  11152. };
  11153. };
  11154. // called when the entityAspect is first created for an entity
  11155. ctor.prototype.startTracking = function(entity, proto) {
  11156. if (!(entity instanceof Backbone.Model)) {
  11157. throw new Error("This entity is not an Backbone.Model instance");
  11158. }
  11159. var stype = entity.entityType || entity.complexType;
  11160. var attributes = entity.attributes;
  11161. // Update so that every data and navigation property has a value.
  11162. stype.dataProperties.forEach(function (dp) {
  11163. if (dp.isComplexProperty) {
  11164. var co = dp.dataType._createInstanceCore(entity, dp.name);
  11165. bbSet.call(entity, dp.name, co);
  11166. } else if (dp.name in attributes) {
  11167. if (bbGet.call(entity, dp.name) === undefined && dp.defaultValue !== undefined) {
  11168. bbSet.call(entity, dp.name, dp.defaultValue);
  11169. }
  11170. } else {
  11171. bbSet.call(entity, dp.name, dp.defaultValue);
  11172. }
  11173. });
  11174. if (stype.navigationProperties) {
  11175. stype.navigationProperties.forEach(function(np) {
  11176. var msg;
  11177. if (np.name in attributes) {
  11178. var val = bbGet.call(entity, np.name);
  11179. if (np.isScalar) {
  11180. if (val && !val.entityType) {
  11181. msg = core.formatString("The value of the '%1' property for entityType: '%2' must be either null or another entity",
  11182. np.name, entity.entityType.name);
  11183. throw new Error(msg);
  11184. }
  11185. } else {
  11186. if (val) {
  11187. if (!val.parentEntity) {
  11188. msg = core.formatString("The value of the '%1' property for entityType: '%2' must be either null or a Breeze relation array",
  11189. np.name, entity.entityType.name);
  11190. throw new Error(msg);
  11191. }
  11192. } else {
  11193. val = breeze.makeRelationArray([], entity, np);
  11194. bbSet.call(entity, np.name, val);
  11195. }
  11196. }
  11197. } else {
  11198. if (np.isScalar) {
  11199. bbSet.call(entity, np.name, null);
  11200. } else {
  11201. val = breeze.makeRelationArray([], entity, np);
  11202. bbSet.call(entity, np.name, val);
  11203. }
  11204. }
  11205. });
  11206. }
  11207. };
  11208. breeze.config.registerAdapter("modelLibrary", ctor);
  11209. // private methods
  11210. }));
  11211. "use strict";
  11212. (function (factory) {
  11213. if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
  11214. // CommonJS or Node: hard-coded dependency on "breeze"
  11215. factory(require("breeze"));
  11216. } else if (typeof define === "function" && define["amd"] && !breeze) {
  11217. // AMD anonymous module with hard-coded dependency on "breeze"
  11218. define(["breeze"], factory);
  11219. } else {
  11220. // <script> tag: use the global `breeze` object
  11221. factory(breeze);
  11222. }
  11223. }(function(breeze) {
  11224. var core = breeze.core;
  11225. var ComplexAspect = breeze.ComplexAspect;
  11226. var ctor = function() {
  11227. this.name = "backingStore";
  11228. };
  11229. ctor.prototype.initialize = function() {
  11230. };
  11231. ctor.prototype.getTrackablePropertyNames = function (entity) {
  11232. var names = [];
  11233. for (var p in entity) {
  11234. if (p === "_$typeName") continue;
  11235. var val = entity[p];
  11236. if (!core.isFunction(val)) {
  11237. names.push(p);
  11238. }
  11239. }
  11240. return names;
  11241. };
  11242. ctor.prototype.initializeEntityPrototype = function (proto) {
  11243. proto.getProperty = function(propertyName) {
  11244. return this[propertyName];
  11245. };
  11246. proto.setProperty = function (propertyName, value) {
  11247. if (!this._backingStore.hasOwnProperty(propertyName)) {
  11248. throw new Error("Unknown property name:" + propertyName);
  11249. }
  11250. this[propertyName] = value;
  11251. // allow setProperty chaining.
  11252. return this;
  11253. };
  11254. //// called after any create during a query;
  11255. // this method cannot be called while a 'defineProperty' accessor is executing
  11256. // because of IE bug
  11257. proto.initializeFrom = function(rawEntity) {
  11258. // copy unmapped properties from newly created client entity to the rawEntity.
  11259. var that = this;
  11260. this.entityType.unmappedProperties.forEach(function(prop) {
  11261. var propName = prop.name;
  11262. rawEntity[propName] = that[propName];
  11263. });
  11264. // this._backingStore = rawEntity;
  11265. if (!this._backingStore) {
  11266. this._backingStore = { };
  11267. }
  11268. };
  11269. // internal implementation details - ugly because of IE9 issues with defineProperty.
  11270. proto._pendingSets = [];
  11271. proto._pendingSets.schedule = function(entity, propName, value) {
  11272. this.push({ entity: entity, propName: propName, value: value });
  11273. if (!this.isPending) {
  11274. this.isPending = true;
  11275. var that = this;
  11276. setTimeout(function() { that.process(); });
  11277. }
  11278. };
  11279. proto._pendingSets.process = function() {
  11280. if (this.length === 0) return;
  11281. this.forEach(function(ps) {
  11282. if (!ps.entity._backingStore) {
  11283. ps.entity._backingStore = { };
  11284. }
  11285. ps.entity._backingStore[ps.propName] = ps.value;
  11286. });
  11287. this.length = 0;
  11288. this.isPending = false;
  11289. };
  11290. movePropDefsToProto(proto);
  11291. };
  11292. // entity is either an entity or a complexObject
  11293. ctor.prototype.startTracking = function (entity, proto) {
  11294. // can't touch the normal property sets within this method - access the backingStore directly instead.
  11295. proto._pendingSets.process();
  11296. var bs = movePropsToBackingStore(entity);
  11297. var that = this;
  11298. // assign default values to the entity
  11299. var stype = entity.entityType || entity.complexType;
  11300. stype.getProperties().forEach(function(prop) {
  11301. var propName = prop.name;
  11302. var val = entity[propName];
  11303. if (prop.isDataProperty) {
  11304. if (prop.isComplexProperty) {
  11305. var co = prop.dataType._createInstanceCore(entity, prop.name);
  11306. bs[propName] = co;
  11307. } else if (val === undefined) {
  11308. bs[propName] = prop.defaultValue;
  11309. }
  11310. } else if (prop.isNavigationProperty) {
  11311. if (val !== undefined) {
  11312. throw new Error("Cannot assign a navigation property in an entity ctor.: " + prop.Name);
  11313. }
  11314. if (prop.isScalar) {
  11315. // TODO: change this to nullstob later.
  11316. bs[propName] = null;
  11317. } else {
  11318. bs[propName] = breeze.makeRelationArray([], entity, prop);
  11319. }
  11320. } else {
  11321. throw new Error("unknown property: " + propName);
  11322. }
  11323. });
  11324. };
  11325. // private methods
  11326. function movePropDefsToProto(proto) {
  11327. var stype = proto.entityType || proto.complexType;
  11328. stype.getProperties().forEach(function(prop) {
  11329. var propName = prop.name;
  11330. if (!proto[propName]) {
  11331. Object.defineProperty(proto, propName, makePropDescription(prop));
  11332. }
  11333. });
  11334. }
  11335. // this method cannot be called while a 'defineProperty' accessor is executing
  11336. // because of IE bug mentioned above.
  11337. function movePropsToBackingStore(instance) {
  11338. var proto = Object.getPrototypeOf(instance);
  11339. if (!instance._backingStore) {
  11340. instance._backingStore = { };
  11341. }
  11342. var stype = proto.entityType || proto.complexType;
  11343. stype.getProperties().forEach(function(prop) {
  11344. var propName = prop.name;
  11345. if (!instance.hasOwnProperty(propName)) return;
  11346. var value = instance[propName];
  11347. delete instance[propName];
  11348. instance[propName] = value;
  11349. });
  11350. return instance._backingStore;
  11351. }
  11352. function makePropDescription(property) {
  11353. var propName = property.name;
  11354. var getAccessorFn = function(backingStore) {
  11355. return function() {
  11356. if (arguments.length == 0) {
  11357. return backingStore[propName];
  11358. } else {
  11359. backingStore[propName] = arguments[0];
  11360. }
  11361. };
  11362. };
  11363. return {
  11364. get: function() {
  11365. var bs = this._backingStore;
  11366. if (!bs) {
  11367. this._pendingSets.process();
  11368. bs = this._backingStore;
  11369. if (!bs) return;
  11370. }
  11371. return bs[propName];
  11372. },
  11373. set: function(value) {
  11374. var bs = this._backingStore;
  11375. if (!bs) {
  11376. this._pendingSets.schedule(this, propName, value);
  11377. return;
  11378. }
  11379. var accessorFn = getAccessorFn(bs);
  11380. if (this._$interceptor) {
  11381. this._$interceptor(property, value, accessorFn);
  11382. } else {
  11383. accessorFn(value);
  11384. }
  11385. },
  11386. enumerable: true,
  11387. configurable: true
  11388. };
  11389. }
  11390. breeze.config.registerAdapter("modelLibrary", ctor);
  11391. }));
  11392. "use strict";
  11393. (function (factory) {
  11394. if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
  11395. // CommonJS or Node: hard-coded dependency on "breeze"
  11396. factory(require("breeze"));
  11397. } else if (typeof define === "function" && define["amd"] && !breeze) {
  11398. // AMD anonymous module with hard-coded dependency on "breeze"
  11399. define(["breeze"], factory);
  11400. } else {
  11401. // <script> tag: use the global `breeze` object
  11402. factory(breeze);
  11403. }
  11404. }(function(breeze) {
  11405. var core = breeze.core;
  11406. var ko;
  11407. var ctor = function () {
  11408. this.name = "ko";
  11409. };
  11410. ctor.prototype.initialize = function () {
  11411. ko = core.requireLib("ko", "The Knockout library");
  11412. ko.extenders.intercept = function(target, interceptorOptions) {
  11413. var instance = interceptorOptions.instance;
  11414. var property = interceptorOptions.property;
  11415. // create a computed observable to intercept writes to our observable
  11416. var result;
  11417. if (target.splice) {
  11418. result = ko.computed({
  11419. read: target //always return the original observables value
  11420. });
  11421. } else {
  11422. result = ko.computed({
  11423. read: target, //always return the original observables value
  11424. write: function(newValue) {
  11425. instance._$interceptor(property, newValue, target);
  11426. return instance;
  11427. }
  11428. });
  11429. }
  11430. //return the new computed observable
  11431. return result;
  11432. };
  11433. };
  11434. ctor.prototype.getTrackablePropertyNames = function (entity) {
  11435. var names = [];
  11436. for (var p in entity) {
  11437. if (p === "entityType") continue;
  11438. if (p === "_$typeName") continue;
  11439. var val = entity[p];
  11440. if (ko.isObservable(val)) {
  11441. names.push(p);
  11442. } else if (!core.isFunction(val)) {
  11443. names.push(p);
  11444. }
  11445. }
  11446. return names;
  11447. };
  11448. ctor.prototype.initializeEntityPrototype = function (proto) {
  11449. proto.getProperty = function(propertyName) {
  11450. return this[propertyName]();
  11451. };
  11452. proto.setProperty = function(propertyName, value) {
  11453. this[propertyName](value);
  11454. // allow set property chaining.
  11455. return this;
  11456. };
  11457. };
  11458. ctor.prototype.startTracking = function (entity, proto) {
  11459. // create ko's for each property and assign defaultValues
  11460. // force unmapped properties to the end
  11461. var stype = entity.entityType || entity.complexType;
  11462. stype.getProperties().sort(function (p1, p2) {
  11463. var v1 = p1.isUnmapped ? 1 : 0;
  11464. var v2 = p2.isUnmapped ? 1 : 0;
  11465. return v1 - v2;
  11466. }).forEach(function(prop) {
  11467. var propName = prop.name;
  11468. var val = entity[propName];
  11469. var koObj;
  11470. // check if property is already exposed as a ko object
  11471. if (ko.isObservable(val)) {
  11472. // if so
  11473. if (prop.isNavigationProperty) {
  11474. throw new Error("Cannot assign a navigation property in an entity ctor.: " + prop.Name);
  11475. }
  11476. koObj = val;
  11477. } else {
  11478. // if not
  11479. if (prop.isDataProperty) {
  11480. if (prop.isComplexProperty) {
  11481. val = prop.dataType._createInstanceCore(entity, prop.name);
  11482. } else if (val === undefined) {
  11483. val = prop.defaultValue;
  11484. }
  11485. koObj = ko.observable(val);
  11486. } else if (prop.isNavigationProperty) {
  11487. if (val !== undefined) {
  11488. throw new Error("Cannot assign a navigation property in an entity ctor.: " + prop.Name);
  11489. }
  11490. if (prop.isScalar) {
  11491. // TODO: change this to nullEntity later.
  11492. koObj = ko.observable(null);
  11493. } else {
  11494. val = breeze.makeRelationArray([], entity, prop);
  11495. koObj = ko.observableArray(val);
  11496. val._koObj = koObj;
  11497. // new code to suppress extra breeze notification when
  11498. // ko's array methods are called.
  11499. koObj.subscribe(onBeforeChange, null, "beforeChange");
  11500. // code to insure that any direct breeze changes notify ko
  11501. val.arrayChanged.subscribe(onArrayChanged);
  11502. //// old code to suppress extra breeze notification when
  11503. //// ko's array methods are called.
  11504. //koObj.subscribe(function(b) {
  11505. // koObj._suppressBreeze = true;
  11506. //}, null, "beforeChange");
  11507. //// code to insure that any direct breeze changes notify ko
  11508. //val.arrayChanged.subscribe(function(args) {
  11509. // if (koObj._suppressBreeze) {
  11510. // koObj._suppressBreeze = false;
  11511. // } else {
  11512. // koObj.valueHasMutated();
  11513. // }
  11514. //});
  11515. koObj.equalityComparer = function() {
  11516. throw new Error("Collection navigation properties may NOT be set.");
  11517. };
  11518. }
  11519. } else {
  11520. throw new Error("unknown property: " + propName);
  11521. }
  11522. }
  11523. if (prop.isNavigationProperty && !prop.isScalar) {
  11524. entity[propName] = koObj;
  11525. } else {
  11526. var koExt = koObj.extend({ intercept: { instance: entity, property: prop } });
  11527. entity[propName] = koExt;
  11528. }
  11529. });
  11530. };
  11531. function onBeforeChange(args) {
  11532. args._koObj._suppressBreeze = true;
  11533. }
  11534. function onArrayChanged(args) {
  11535. var koObj = args.relationArray._koObj;
  11536. if (koObj._suppressBreeze) {
  11537. koObj._suppressBreeze = false;
  11538. } else {
  11539. koObj.valueHasMutated();
  11540. }
  11541. }
  11542. breeze.config.registerAdapter("modelLibrary", ctor);
  11543. }));
  11544. // set defaults
  11545. breeze.config.initializeAdapterInstances({
  11546. ajax: "jQuery",
  11547. dataService: "webApi"
  11548. });
  11549. // don't initialize with ko unless it exists.
  11550. var ko = window.ko;
  11551. if ((!ko) && window.require) {
  11552. ko = window.require("ko");
  11553. }
  11554. if (ko) {
  11555. breeze.config.initializeAdapterInstance("modelLibrary", "ko");
  11556. } else {
  11557. breeze.config.initializeAdapterInstance("modelLibrary", "backingStore");
  11558. }
  11559. this.window.breeze = breeze;
  11560. return breeze;
  11561. });