From 889504070d5f30f38269f13b1f98834a1776d034 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Fri, 16 May 2014 10:23:01 -0600 Subject: [PATCH] Various improvements. --- Gruntfile.js | 14 ++++++++- bower.json | 4 +-- dist/angular-data.js | 10 ++++++- dist/angular-data.min.js | 2 +- guide/angular-data-mocks/index.doc | 8 ++++++ guide/angular-data-mocks/overview.doc | 12 ++++++++ guide/angular-data-mocks/setup.doc | 8 ++++++ guide/angular-data-mocks/testing.doc | 8 ++++++ guide/angular-data.css | 6 +++- guide/angular-data/resource/resource.doc | 30 ++++++++++++++++++++ guide/angular-data/resources.doc | 2 +- guide/angular-data/synchronous.doc | 11 +++++++- guide/nav.html | 36 ++++++++++++++---------- src/datastore/sync_methods/inject.js | 10 ++++++- 14 files changed, 137 insertions(+), 24 deletions(-) create mode 100644 guide/angular-data-mocks/index.doc create mode 100644 guide/angular-data-mocks/overview.doc create mode 100644 guide/angular-data-mocks/setup.doc create mode 100644 guide/angular-data-mocks/testing.doc diff --git a/Gruntfile.js b/Gruntfile.js index 7484d07..7801828 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -239,6 +239,17 @@ module.exports = function (grunt) { storage: 5 } }, + { + id: 'angular-data-mocks', + title: 'angular-data-mocks', + docs: ['guide/angular-data-mocks/'], + rank: { + index: 1, + overview: 2, + setup: 3, + testing: 4 + } + }, { id: 'angular-data-resource', title: 'Defining Resources', @@ -248,7 +259,8 @@ module.exports = function (grunt) { overview: 2, basic: 3, advanced: 4, - lifecycle: 5 + lifecycle: 5, + custom: 6 } }, { diff --git a/bower.json b/bower.json index 9b3cb4f..74e7a41 100644 --- a/bower.json +++ b/bower.json @@ -28,8 +28,8 @@ "devDependencies": { "angular": "~1.2.16", "angular-mocks": "~1.2.16", - "angular-data-mocks": "0.2.0", "angular-cache": "~3.0.0-beta.4", - "observe-js": "~0.2.0" + "observe-js": "~0.2.0", + "angular-data-mocks": "~0.3.1" } } diff --git a/dist/angular-data.js b/dist/angular-data.js index b70815d..90bc802 100644 --- a/dist/angular-data.js +++ b/dist/angular-data.js @@ -3956,7 +3956,15 @@ function _inject(definition, resource, attrs) { item = this.get(definition.name, id); if (!item) { - item = definition.class ? new definition[definition.class]() : {}; + if (definition.class) { + if (attrs instanceof definition[definition.class]) { + item = attrs; + } else { + item = new definition[definition.class](); + } + } else { + item = {}; + } resource.previousAttributes[id] = {}; _this.utils.deepMixIn(item, attrs); diff --git a/dist/angular-data.min.js b/dist/angular-data.min.js index 3416cc8..7753c06 100644 --- a/dist/angular-data.min.js +++ b/dist/angular-data.min.js @@ -8,4 +8,4 @@ * @overview Data store for Angular.js. */ require=function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;gb&&a.check();)a.report(),b++}function e(a){for(var b in a)return!1;return!0}function f(a){return e(a.added)&&e(a.removed)&&e(a.changed)}function g(a,b){var c={},d={},e={};for(var f in b){var g=a[f];(void 0===g||g!==b[f])&&(f in a?g!==b[f]&&(e[f]=g):d[f]=void 0)}for(var f in a)f in b||(c[f]=a[f]);return Array.isArray(a)&&a.length!==b.length&&(e.length=a.length),{added:c,removed:d,changed:e}}function h(a,b){var c=b||(Array.isArray(a)?[]:{});for(var d in a)c[d]=a[d];return Array.isArray(a)&&(c.length=a.length),c}function i(a,b,c,d){if(this.closed=!1,this.object=a,this.callback=b,this.target=c,this.token=d,this.reporting=!0,n){var e=this;this.boundInternalCallback=function(a){e.internalCallback(a)}}j(this),this.connect(),this.sync(!0)}function j(a){u&&(t.push(a),i._allObserversCount++)}function k(a,b,c,d){i.call(this,a,b,c,d)}function l(a){this.arr=[],this.callback=a,this.isObserved=!0}function m(a,b,c){for(var d={},e={},f=0;fa&&b.anyChanged);i._allObserversCount=t.length,v=!1}}},u&&(a.Platform.clearObservers=function(){t=[]}),k.prototype=r({__proto__:i.prototype,connect:function(){n&&Object.observe(this.object,this.boundInternalCallback)},sync:function(){n||(this.oldObject=h(this.object))},check:function(a){var b,c;if(n){if(!a)return!1;c={},b=m(this.object,a,c)}else c=this.oldObject,b=g(this.object,this.oldObject);return f(b)?!1:(this.reportArgs=[b.added||{},b.removed||{},b.changed||{}],this.reportArgs.push(function(a){return c[a]}),!0)},disconnect:function(){n?this.object&&Object.unobserve(this.object,this.boundInternalCallback):this.oldObject=void 0}});var x=Object.getPrototypeOf({}),y=Object.getPrototypeOf([]);l.prototype={reset:function(){this.isObserved=!this.isObserved},observe:function(a){if(c(a)&&a!==x&&a!==y){var b=this.arr.indexOf(a);b>=0&&this.arr[b+1]===this.isObserved||(0>b&&(b=this.arr.length,this.arr[b]=a,Object.observe(a,this.callback)),this.arr[b+1]=this.isObserved,this.observe(Object.getPrototypeOf(a)))}},cleanup:function(){for(var a=0,b=0,c=this.isObserved;ba&&(this.arr[a]=d,this.arr[a+1]=c),a+=2):Object.unobserve(d,this.callback),b+=2}this.arr.length=a}};var z={"new":!0,updated:!0,deleted:!0};a.Observer=i,a.Observer.hasObjectObserve=n,a.ObjectObserver=k}((c.Number={isNaN:window.isNaN})?c:c)}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],observejs:[function(a,b){b.exports=a("QYwGEY")},{}],3:[function(a,b){function c(a,b){return-1!==d(a,b)}var d=a("./indexOf");b.exports=c},{"./indexOf":6}],4:[function(a,b){function c(a,b,c){b=d(b,c);var e=[];if(null==a)return e;for(var f,g=-1,h=a.length;++gc?d+c:c;d>e;){if(a[e]===b)return e;e++}return-1}b.exports=c},{}],7:[function(a,b){function c(a){return null!=a&&""!==a}function d(a,b){return b=b||"",e(a,c).join(b)}var e=a("./filter");b.exports=d},{"./filter":4}],8:[function(a,b){function c(a,b,c){var d=a.length;b=null==b?0:0>b?Math.max(d+b,0):Math.min(b,d),c=null==c?d:0>c?Math.max(d+c,0):Math.min(c,d);for(var e=[];c>b;)e.push(a[b++]);return e}b.exports=c},{}],9:[function(a,b){function c(a,b){if(null==a)return[];if(a.length<2)return a;null==b&&(b=d);var f,g,h;return f=~~(a.length/2),g=c(a.slice(0,f),b),h=c(a.slice(f,a.length),b),e(g,h,b)}function d(a,b){return b>a?-1:a>b?1:0}function e(a,b,c){for(var d=[];a.length&&b.length;)d.push(c(a[0],b[0])<=0?a.shift():b.shift());return a.length&&d.push.apply(d,a),b.length&&d.push.apply(d,b),d}b.exports=c},{}],10:[function(a,b){function c(a,b){var c={};if(null==a)return c;var e,f=-1,g=a.length;if(d(b))for(;++f"in a?d=d&&c[b]>a[">"]:">="in a?d=d&&c[b]>=a[">="]:"<"in a?d=d&&c[b]e?-1:e>d?1:0:e>d?-1:d>e?1:0})}}return this.utils.isNumber(b.query.limit)&&this.utils.isNumber(b.query.skip)?i=this.utils.slice(i,b.query.skip,Math.min(i.length,b.query.skip+b.query.limit)):this.utils.isNumber(b.query.limit)?i=this.utils.slice(i,0,Math.min(i.length,b.query.limit)):this.utils.isNumber(b.query.skip)&&(i=b.query.skip=b?a+1:b},deepFreeze:function b(a){if("function"==typeof Object.freeze){var c,d;Object.freeze(a);for(d in a)c=a[d],a.hasOwnProperty(d)&&"object"==typeof c&&!Object.isFrozen(c)&&b(c)}},diffObjectFromOldObject:function(a,b){var c={},d={},e={};for(var f in b){var g=a[f];(void 0===g||g!==b[f])&&(f in a?g!==b[f]&&(e[f]=g):d[f]=void 0)}for(var h in a)h in b||(c[h]=a[h]);return{added:c,removed:d,changed:e}}}}]},{"mout/array/contains":3,"mout/array/filter":4,"mout/array/slice":8,"mout/array/sort":9,"mout/array/toLookup":10,"mout/lang/isEmpty":15,"mout/object/deepMixIn":22,"mout/object/forOwn":24,"mout/object/pick":27,"mout/object/set":28,"mout/string/makePath":29,"mout/string/upperCase":30}],utils:[function(a,b){b.exports=a("K0yknU")},{}]},{},[60]); \ No newline at end of file +f=e.filter(a,h,c)}return f});if(b.query.orderBy){if(this.utils.isString(b.query.orderBy)&&(b.query.orderBy=[[b.query.orderBy,"ASC"]]),!this.utils.isArray(b.query.orderBy))throw new this.errors.IllegalArgumentError(d+"params.query.orderBy: Must be a string or an array!",{params:{query:{orderBy:{actual:typeof b.query.orderBy,expected:"string|array"}}}});for(var j=0;je?-1:e>d?1:0:e>d?-1:d>e?1:0})}}return this.utils.isNumber(b.query.limit)&&this.utils.isNumber(b.query.skip)?i=this.utils.slice(i,b.query.skip,Math.min(i.length,b.query.skip+b.query.limit)):this.utils.isNumber(b.query.limit)?i=this.utils.slice(i,0,Math.min(i.length,b.query.limit)):this.utils.isNumber(b.query.skip)&&(i=b.query.skip=b?a+1:b},deepFreeze:function b(a){if("function"==typeof Object.freeze){var c,d;Object.freeze(a);for(d in a)c=a[d],a.hasOwnProperty(d)&&"object"==typeof c&&!Object.isFrozen(c)&&b(c)}},diffObjectFromOldObject:function(a,b){var c={},d={},e={};for(var f in b){var g=a[f];(void 0===g||g!==b[f])&&(f in a?g!==b[f]&&(e[f]=g):d[f]=void 0)}for(var h in a)h in b||(c[h]=a[h]);return{added:c,removed:d,changed:e}}}}]},{"mout/array/contains":3,"mout/array/filter":4,"mout/array/slice":8,"mout/array/sort":9,"mout/array/toLookup":10,"mout/lang/isEmpty":15,"mout/object/deepMixIn":22,"mout/object/forOwn":24,"mout/object/pick":27,"mout/object/set":28,"mout/string/makePath":29,"mout/string/upperCase":30}],utils:[function(a,b){b.exports=a("K0yknU")},{}]},{},[60]); \ No newline at end of file diff --git a/guide/angular-data-mocks/index.doc b/guide/angular-data-mocks/index.doc new file mode 100644 index 0000000..00ff9ce --- /dev/null +++ b/guide/angular-data-mocks/index.doc @@ -0,0 +1,8 @@ +@doc overview +@id index +@name Overview +@description + +# Overview + + diff --git a/guide/angular-data-mocks/overview.doc b/guide/angular-data-mocks/overview.doc new file mode 100644 index 0000000..60d02cb --- /dev/null +++ b/guide/angular-data-mocks/overview.doc @@ -0,0 +1,12 @@ +@doc overview +@id overview +@name Overview +@description + +Angular-data is a fake angular-data implementation suitable for unit testing angular applications that use the `angular-data.DS` module. + +__Version:__ 0.3.0 + +__angular-data-mocks requires [sinon](http://sinonjs.org/) to be loaded in order to work.__ + +Refer to the [angular-data-mocks API](/documentation/api/angular-data-mocks/angular-data-mocks) for more detailed information. diff --git a/guide/angular-data-mocks/setup.doc b/guide/angular-data-mocks/setup.doc new file mode 100644 index 0000000..dd9b1a5 --- /dev/null +++ b/guide/angular-data-mocks/setup.doc @@ -0,0 +1,8 @@ +@doc overview +@id setup +@name Setting Up +@description + +TODO: Explain how to set up angular tests to use angular-data-mocks. + +Refer to the [angular-data-mocks API](/documentation/api/angular-data-mocks/angular-data-mocks) for more detailed information. diff --git a/guide/angular-data-mocks/testing.doc b/guide/angular-data-mocks/testing.doc new file mode 100644 index 0000000..2c33159 --- /dev/null +++ b/guide/angular-data-mocks/testing.doc @@ -0,0 +1,8 @@ +@doc overview +@id testing +@name Testing +@description + +TODO: Explain how to use angular-data-mocks in angular tests. + +Refer to the [angular-data-mocks API](/documentation/api/angular-data-mocks/angular-data-mocks) for more detailed information. diff --git a/guide/angular-data.css b/guide/angular-data.css index 830366b..d7d2ca0 100644 --- a/guide/angular-data.css +++ b/guide/angular-data.css @@ -1,13 +1,17 @@ body { background-size: auto; background: url(/resources/img/cream_dust.png) repeat repeat; - font-family:'Open sans',Arial,Helvetica,sans-serif; + font-family: 'Open sans', Arial, Helvetica, sans-serif; } .main-nav { border-bottom: 2px solid #0088cc; } +.main-nav .dropdown-menu { + min-width: 250px; +} + .main-nav:before { content: " "; position: absolute; diff --git a/guide/angular-data/resource/resource.doc b/guide/angular-data/resource/resource.doc index ec3540b..52fe1c1 100644 --- a/guide/angular-data/resource/resource.doc +++ b/guide/angular-data/resource/resource.doc @@ -167,3 +167,33 @@ angular.module('myApp', ['angular-data.DS']) }); ``` + +@doc overview +@id custom +@name Custom Model Behavior +@description + +If you provide a `methods` field in the options passed to `DS.defineResource`, angular-data wrap items of that resource +with an empty constructor function. The `methods` option should be an object where the keys are method names and the +values are functions. This object will be mixed in to the prototype empty constructor function used to wrap items of the +new resource. In this way you can add custom behavior to what will now be "instances" of the new resource. + +## Example: +```js +DS.defineResource({ + name: 'user', + methods: { + fullName: function () { + return this.first + ' ' + this.last; + } + } +}); + +DS.inject('user', { id: 1, first: 'John', last: 'Anderson' }); + +var user = DS.get('user', 1); + +user.fullName(); // "John Anderson" + +user.constructor; // function User() { ... } +``` diff --git a/guide/angular-data/resources.doc b/guide/angular-data/resources.doc index f2581be..80d0b55 100644 --- a/guide/angular-data/resources.doc +++ b/guide/angular-data/resources.doc @@ -18,4 +18,4 @@ myApp.run(function (DS) { }); ``` -See the [Resource Guide](/documentation/guide/resource/index) for detailed information on defining resources. +See the [Resource Guide](/documentation/guide/angular-data-resource/index) for detailed information on defining resources. diff --git a/guide/angular-data/synchronous.doc b/guide/angular-data/synchronous.doc index 81b874a..559e1a1 100644 --- a/guide/angular-data/synchronous.doc +++ b/guide/angular-data/synchronous.doc @@ -25,10 +25,19 @@ $scope.$watch(function () { // When this callback is executed, it means that the data store thinks the item changed // Retrieve the updated item from the data store's cache - $scope.document = DS.get('document', 45); + $scope.myDoc = DS.get('document', 45); }); ``` +To make things simpler, angular-data has some bind methods to help with this: + +```js +DS.bindOne($scope, 'myDoc', 'document', 45'); +``` + +The above example shows how to bind an item in the data store to the stop. Whenever that item changes it will be updated +on the $scope. + When the app starts up, the calls to `lastModified()` and `get()` will both returned undefined, because the item isn't in the data store yet. If we insert the statement: `DS.find('document', 45);` right above the `$watch` function, the data store will make an AJAX request for that item. When the item returns from the server, the last modified timestamp for that item will change diff --git a/guide/nav.html b/guide/nav.html index 1a7f2a8..bb0edd8 100644 --- a/guide/nav.html +++ b/guide/nav.html @@ -26,7 +26,7 @@ Basics
  • - Defining Resources + Defining Resources
  • Synchronous Methods @@ -35,6 +35,11 @@ Asynchronous Methods
  • + +
  • + Testing Guide +
  • +
  • Overview @@ -59,9 +64,6 @@
  • + +
  • + Issues +
  • +
  • + GitHub +
  • +
  • Mailing List diff --git a/src/datastore/sync_methods/inject.js b/src/datastore/sync_methods/inject.js index 0ce7aee..9734d7a 100644 --- a/src/datastore/sync_methods/inject.js +++ b/src/datastore/sync_methods/inject.js @@ -34,7 +34,15 @@ function _inject(definition, resource, attrs) { item = this.get(definition.name, id); if (!item) { - item = definition.class ? new definition[definition.class]() : {}; + if (definition.class) { + if (attrs instanceof definition[definition.class]) { + item = attrs; + } else { + item = new definition[definition.class](); + } + } else { + item = {}; + } resource.previousAttributes[id] = {}; _this.utils.deepMixIn(item, attrs);