├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── README.md ├── bower.json ├── dist └── angular-jsonld.js ├── package.json ├── src ├── angularJsonld.module.js ├── providers │ ├── jsonld.provider.js │ └── jsonldContext.provider.js ├── services │ └── jsonldRest.service.js └── vocabularies │ ├── hydraCore.module.js │ └── schema.org.module.js └── test ├── .jshintrc ├── karma.conf.js └── spec ├── angularJsonldSpec.js └── services └── jsonldRestSpec.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "es3": false, 7 | "forin": true, 8 | "freeze": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": "nofunc", 12 | "newcap": true, 13 | "noarg": true, 14 | "noempty": true, 15 | "nonbsp": true, 16 | "nonew": true, 17 | "plusplus": false, 18 | "quotmark": "single", 19 | "undef": true, 20 | "unused": false, 21 | "strict": false, 22 | "maxparams": 10, 23 | "maxdepth": 5, 24 | "maxstatements": 40, 25 | "maxcomplexity": 8, 26 | "maxlen": 120, 27 | 28 | "asi": false, 29 | "boss": false, 30 | "debug": false, 31 | "eqnull": true, 32 | "esnext": false, 33 | "evil": false, 34 | "expr": false, 35 | "funcscope": false, 36 | "globalstrict": false, 37 | "iterator": false, 38 | "lastsemic": false, 39 | "laxbreak": false, 40 | "laxcomma": false, 41 | "loopfunc": true, 42 | "maxerr": false, 43 | "moz": false, 44 | "multistr": false, 45 | "notypeof": false, 46 | "proto": false, 47 | "scripturl": false, 48 | "shadow": false, 49 | "sub": true, 50 | "supernew": false, 51 | "validthis": false, 52 | "noyield": false, 53 | 54 | "browser": true, 55 | "node": true, 56 | 57 | "globals": { 58 | "angular": false, 59 | "$": false 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // # Globbing 4 | // for performance reasons we're only matching one level down: 5 | // 'test/spec/{,*/}*.js' 6 | // use this if you want to recursively match all subfolders: 7 | // 'test/spec/**/*.js' 8 | 9 | module.exports = function(grunt) { 10 | 11 | // Load grunt tasks automatically 12 | require('load-grunt-tasks')(grunt); 13 | 14 | // Time how long tasks take. Can help when optimizing build times 15 | require('time-grunt')(grunt); 16 | 17 | // Define the configuration for all the tasks 18 | grunt.initConfig({ 19 | 20 | // Make sure code styles are up to par and there are no obvious mistakes 21 | jshint: { 22 | options: { 23 | jshintrc: '.jshintrc', 24 | reporter: require('jshint-stylish') 25 | }, 26 | all: { 27 | src: [ 28 | 'Gruntfile.js', 29 | 'src/{,*/}*.js' 30 | ] 31 | }, 32 | test: { 33 | options: { 34 | jshintrc: 'test/.jshintrc' 35 | }, 36 | src: ['test/spec/{,*/}*.js'] 37 | } 38 | }, 39 | 40 | // Empties folders to start fresh 41 | clean: { 42 | dist: { 43 | files: [{ 44 | dot: true, 45 | src: [ 46 | 'dist/{,*/}*', 47 | '!dist/.git*' 48 | ] 49 | }] 50 | } 51 | }, 52 | 53 | concat: { 54 | options: { 55 | banner: '\'use strict\';\n', 56 | process: function(src, filepath) { 57 | return '// Source: ' + 58 | filepath + '\n' + 59 | src.replace(/(^|\n)[ \t]*('use strict'|"use strict");?\s*/g, '$1'); 60 | } 61 | }, 62 | dist: { 63 | src: ['src/angularJsonld.module.js', 'src/*/*.js'], 64 | dest: 'dist/angular-jsonld.js', 65 | }, 66 | }, 67 | 68 | // Test settings 69 | karma: { 70 | unit: { 71 | configFile: 'test/karma.conf.js', 72 | singleRun: true 73 | }, 74 | server: { 75 | configFile: 'test/karma.conf.js', 76 | autoWatch: true 77 | } 78 | } 79 | }); 80 | 81 | grunt.registerTask('test', [ 82 | 'clean', 83 | 'karma:unit' 84 | ]); 85 | 86 | grunt.registerTask('build', [ 87 | 'clean', 88 | 'jshint', 89 | 'test', 90 | 'concat' 91 | ]); 92 | 93 | grunt.registerTask('default', [ 94 | 'build' 95 | ]); 96 | }; 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-jsonld [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/io-informatics/angular-jsonld?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 2 | This angular module facilitates the integration of [JSON-LD](http://json-ld.org) server APIs in AngularJS clients. It is implemented on top of [Restagular](https://github.com/mgonto/restangular). Its purpose is to provide an adapter layer to map client's data model to the server's API model by using the semantics embedded in JSON-LD as the contract interface. Another important 3 | functionality of *angular-jsonld* is to enable easy navigation of JSON-LD hyperlinks in client's code. We further explain the meaning of these two feature bellow. 4 | 5 | 6 | ## Mapping local objects to JSON-LD representations 7 | As mentioned before one of the intend of this library is that you can semantically map your local (JS) objects to the representations obtained from the server. For example let's say your server provides you with the following representation: 8 | 9 | ```json 10 | { 11 | "@context": "http://schema.org/", 12 | "@type": "Person", 13 | "name": "Jane Doe", 14 | "jobTitle": "Professor", 15 | "telephone": "(425) 123-4567", 16 | "url": "http://www.janedoe.com" 17 | } 18 | ``` 19 | On one side we could consume this JSONLD representation as you would with any other JSON representation (i.e at the syntactic level). For instance: 20 | 21 | ```javascript 22 | $http.get('http://example.org/person/1').success(function(res){ 23 | console.log('Hello ',res.name); 24 | }); 25 | ``` 26 | 27 | The code above assumes a specific syntactic representation of the response, but if you do such thing why bother with JSONLD, right? In fact, your server could send you the following JSONLD representation instead: 28 | ```json 29 | { 30 | "@context": {}, 31 | "@type": "http://schema.org/Person", 32 | "http://schema.org/name": "Jane Doe", 33 | "http://schema.org/jobTitle": "Professor", 34 | "http://schema.org/telephone": "(425) 123-4567", 35 | "http://schema.org/url": "http://www.janedoe.com" 36 | } 37 | ``` 38 | or... 39 | ```json 40 | { 41 | "@context": { 42 | "schema": "http://schema.org/" 43 | }, 44 | "@type": "schema:Person", 45 | "schema:name": "Jane Doe", 46 | "schema:jobTitle": "Professor", 47 | "schema:telephone": "(425) 123-4567", 48 | "schema:url": "http://www.janedoe.com" 49 | } 50 | ``` 51 | 52 | Even thought these are syntactically different they both means exactly the same as the JSONLD representation of the first example. However, your client code will break as it will not find a property "name" in the object parsed from the server response. 53 | 54 | *Here is where angular-jsonld comes handy!* 55 | 56 | Instead of working at the syntactic level you can bind a "local context" to the semantic representation provided by the server. Let see the angular-jsonld code: 57 | 58 | ```javascript 59 | var app = angular.module('app', ['angularJsonld']); 60 | 61 | app.controller('HelloCtrl', function(JsonldRest){ 62 | 63 | /* Confiigure the API baseUrl */ 64 | JsonldRest.setBaseUrl('http://example.org'); 65 | 66 | /* A handler to a server collection of persons with a local context interpretation */ 67 | var people = JsonldRest.collection('/person').withContext({ 68 | "schema": "http://schema.org/", 69 | "fullName": "schema:name" /* My client calls 'fullName' the http://schema.org/name property*/ property */ 70 | }); 71 | 72 | /* We retrieve the person http://example.org/person/1 */ 73 | people.one('1').get().then(function(res){ 74 | console.log("Hello ", res.fullName); 75 | }); 76 | 77 | }) 78 | ``` 79 | 80 | Now, as you can see we decoupled from the syntactic representation and just map our own data model (i.e. fullName) to the semantic property http://schema.org/name. No matter which of the the three different representations showed above we get from the server, our client will always work. Furthermore, if the server changes (semantically) or we connect to a different API it is easy to adapt our client by just changing the local context. Actually you can provide a local context which is global to the entire application such that a refactor like such is even easier. For instance: 81 | 82 | ```javascript 83 | var app = angular.module('app', ['angularJsonld']); 84 | 85 | app.config(function(jsonldContextProvider){ 86 | /* If we need to change the semantics of 'fullName' we just do it here for the entire application */ 87 | jsonldContextProvider.add({ 88 | "schema": "http://schema.org/", 89 | "fullName": "schema:name" /* My client calls 'fullName' the http://schema.org/name property*/ 90 | }); 91 | }); 92 | 93 | app.controller('HelloCtrl', function(JsonldRest){ 94 | 95 | /* Confiigure the API baseUrl */ 96 | JsonldRest.setBaseUrl('http://example.org'); 97 | 98 | /* A handler to a server collection of persons with a local context interpretation */ 99 | var people = JsonldRest.collection('/person'); 100 | 101 | /* We retrieve the person http://example.org/person/1 */ 102 | people.one('1').get().then(function(res){ 103 | console.log("Hello ", res.fullName); 104 | }); 105 | 106 | }) 107 | ``` 108 | 109 | ## Link dereferencing 110 | JSONLD is about [Linked Data](http://linkeddata.org), and linked data is mostly about *Linking* :bowtie:. If you are using JSONLD in your API is probably because you are building [Hypermedia REST API](http://www.blueprintforge.com/blog/2012/01/01/a-short-explanation-of-hypermedia-controls-in-restful-services/) and you expect clients to easily follow links in your JSONLD representations. For instance let's say we add a list of friends links to the Person representation from the examples of the previous section: 111 | 112 | ```json 113 | { 114 | "@context": ["http://schema.org/", { 115 | "foaf": "http://xmlns.com/foaf/0.1/", 116 | "knows": { 117 | "@id": "foaf:knows", 118 | "@type": "@id" 119 | } 120 | }], 121 | "@type": "Person", 122 | "name": "Jane Doe", 123 | "jobTitle": "Professor", 124 | "telephone": "(425) 123-4567", 125 | "url": "http://www.janedoe.com", 126 | "knows": [ "http://example.org/2", "http://example.org/3"] 127 | } 128 | ``` 129 | You can easily navigate these links with angular-jsonld. Let's look at the code: 130 | 131 | ```javascript 132 | var app = angular.module('app', ['angularJsonld']); 133 | 134 | app.config(function(jsonldContextProvider){ 135 | /* If we need to change the semantics of 'fullName' we just do it here for the entire application */ 136 | jsonldContextProvider.add({ 137 | "schema": "http://schema.org/", 138 | "foaf": "http://xmlns.com/foaf/0.1/", 139 | "fullName": "schema:name" /* My client calls 'fullName' the http://schema.org/name property */ 140 | "friends": "foaf:knows" /* My client calls 'friends' the http://xmlns.com/foaf/0.1/knows property */ 141 | }); 142 | }); 143 | 144 | app.controller('HelloCtrl', function(JsonldRest){ 145 | 146 | /* Confiigure the API baseUrl */ 147 | JsonldRest.setBaseUrl('http://example.org'); 148 | 149 | /* A handler to a server collection of persons with a local context interpretation */ 150 | var people = JsonldRest.collection('/person'); 151 | 152 | /* We retrieve the person http://example.org/person/1 */ 153 | people.one('1').get().then(function(res){ 154 | console.log(res.fullName+' is friend with: '); 155 | res.friends.forEach(function(friendLink){ 156 | /* Navigate to the friend resource */ 157 | friendLink.get().then(function(friend){ 158 | console.log(friend.fullName); 159 | }); 160 | }); 161 | }); 162 | 163 | }) 164 | ``` 165 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-jsonld", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "angular": "~1.4.0", 6 | "restangular": "~1.5.0", 7 | "jsonld": "~0.3.6" 8 | }, 9 | "devDependencies": { 10 | "angular-mocks": "~1.4.0", 11 | "angular-scenario": "~1.4.0" 12 | }, 13 | "main": "dist/angular-jsonld.js" 14 | } 15 | -------------------------------------------------------------------------------- /dist/angular-jsonld.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // Source: src/angularJsonld.module.js 3 | (function() { 4 | angular 5 | .module('angularJsonld', ['restangular']); 6 | 7 | })(); 8 | 9 | // Source: src/providers/jsonld.provider.js 10 | /* global jsonld */ 11 | (function(jsonld) { 12 | angular 13 | .module('angularJsonld') 14 | .provider('jsonld', JsonldProvider); 15 | 16 | /* @ngInject */ 17 | function JsonldProvider() { 18 | var knownContexts = {}; 19 | var provider = this; 20 | 21 | /* @ngInject */ 22 | provider.$get = function($log) { 23 | var nodeDocumentLoader = jsonld.documentLoaders.xhr(); 24 | 25 | var customLoader = function(uri, callback) { 26 | if(uri in knownContexts) { 27 | $log.debug('Returning known context:', knownContexts[uri]); 28 | return callback( 29 | null, { 30 | contextUrl: null, // this is for a context via a link header 31 | document: knownContexts[uri], // this is the actual document that was loaded 32 | documentUrl: uri // this is the actual context URL after redirects 33 | }); 34 | } 35 | nodeDocumentLoader(uri).then(function(response){ 36 | callback(null, response); 37 | }). 38 | catch(function(err){ 39 | callback(err, null); 40 | }); 41 | }; 42 | jsonld.documentLoader = customLoader; 43 | return jsonld; 44 | }; 45 | 46 | provider.registerContext = function(uri, context) { 47 | knownContexts[uri] = context; 48 | }; 49 | } 50 | 51 | })(jsonld); 52 | 53 | // Source: src/providers/jsonldContext.provider.js 54 | (function() { 55 | /** 56 | * @name angularJsonld.contextProvider 57 | * @description 58 | * Provider to configure JSONLD context 59 | */ 60 | angular 61 | .module('angularJsonld') 62 | .provider('jsonldContext', JsonldContextProvider); 63 | 64 | /* @ngInject */ 65 | function JsonldContextProvider(){ 66 | var provider = this; 67 | var context = {}; 68 | 69 | provider.$get = function() { 70 | return Object.freeze(context); 71 | }; 72 | 73 | provider.add = function(c){ 74 | angular.extend(context, c); 75 | }; 76 | 77 | } 78 | 79 | })(); 80 | 81 | // Source: src/services/jsonldRest.service.js 82 | /* global */ 83 | (function() { 84 | angular 85 | .module('angularJsonld') 86 | .factory('JsonldRest', JsonldRest); 87 | 88 | /* @ngInject */ 89 | function JsonldRest($q, $log, $rootScope, Restangular, jsonld, jsonldContext) { 90 | function JsonldRestangular(context) { 91 | var configuredRestangular = Restangular.withConfig(function(RestangularConfigurer){ 92 | RestangularConfigurer.setRestangularFields({ 93 | selfLink: '@id', 94 | get: '_get' 95 | }); 96 | RestangularConfigurer.setOnElemRestangularized(function(elem, isCollection, what, Restangular){ 97 | return angular.extend(elem,{ 98 | get: jsonldGet(elem, context, isCollection) 99 | }); 100 | }); 101 | }); 102 | 103 | var withConfigFn = configuredRestangular.withConfig; 104 | 105 | return angular.extend(configuredRestangular, { 106 | collection: collection, 107 | resource: resource, 108 | withConfig: function(f){ 109 | return new JsonldRest($q, $log, $rootScope, withConfigFn(f), jsonld, jsonldContext); 110 | }, 111 | withContext: function(c) { 112 | return new JsonldRestangular(c); 113 | } 114 | }); 115 | } 116 | 117 | var restangular = new JsonldRestangular(); 118 | 119 | return restangular; 120 | 121 | function resource(containerRoute, localRoute, context){ 122 | var ra = context? restangular.withContext(context): restangular; 123 | var r = ra.one(containerRoute, localRoute); 124 | return angular.extend(r, { 125 | withContext: function(c) { 126 | return resource(containerRoute, localRoute, c); 127 | } 128 | }); 129 | } 130 | 131 | function collection(route, context){ 132 | var ra = context? restangular.withContext(context): restangular; 133 | 134 | var col = ra.all(route); 135 | 136 | return angular.extend(col,{ 137 | withContext: function(c) { 138 | return collection(route, c); 139 | }, 140 | one: function(elementRoute){ 141 | return resource(route, elementRoute, context); 142 | }, 143 | getList: function(){ 144 | var members = arguments.length > 0? arguments[0] : '@graph'; 145 | var args = arguments.length > 1? Array.prototype.slice.apply(arguments, [1]) : undefined; 146 | return col.get.apply(col, args).then(function(res){ 147 | return angular.extend(asArray(res[members]), res); 148 | }); 149 | } 150 | }); 151 | } 152 | 153 | function asArray(obj){ 154 | if(obj instanceof Array) { 155 | return obj; 156 | } 157 | else { 158 | return [obj]; 159 | } 160 | } 161 | 162 | function restangularize(node, parent){ 163 | if(node instanceof Array){ 164 | return node.map(function(element){ 165 | return restangularize(element, parent); 166 | }); 167 | } 168 | for(var field in node) { 169 | if(node.hasOwnProperty(field) && field !== '@context' && typeof(node[field]) === 'object'){ 170 | node[field] = restangularize(node[field], node); 171 | } 172 | } 173 | if(node['@context']){ 174 | var context = node['@context']; 175 | for(var prop in context){ 176 | if(context.hasOwnProperty(prop) && isTypeCoercionProperty(context, prop, node)){ 177 | node[prop] = restangularize({'@id':node[prop]}, node); 178 | } 179 | } 180 | } 181 | 182 | if(node['@id']) { 183 | var link = restangular.restangularizeElement(parent, node, node['@id']); 184 | $log.info('Created Restangular subresource: ', link); 185 | return angular.extend(link, { 186 | get: jsonldGet(link) 187 | }); 188 | } 189 | else { 190 | return node; 191 | } 192 | } 193 | 194 | function jsonldGet(obj, context, isCollection){ 195 | if(angular.isFunction(obj._get)){ 196 | var doGet = function(params){ 197 | if(isCollection) { 198 | return obj._get('', params); 199 | } 200 | return obj._get(params); 201 | }; 202 | return function(params) { 203 | return doGet(params).then(function(data){ 204 | return compact(data, context); 205 | }).then(function(compacted){ 206 | return restangularize(compacted); 207 | }); 208 | }; 209 | } 210 | return undefined; 211 | } 212 | 213 | function compact(data, context){ 214 | var c = angular.copy(jsonldContext); 215 | if(context){ 216 | angular.extend(c, context); 217 | } 218 | var compactDefer = $q.defer(); 219 | jsonld.compact(data, c, function(err, compacted){ 220 | if(err) { 221 | $log.error('Faild compact jsonld', err); 222 | compactDefer.reject(err); 223 | } 224 | else { 225 | $log.debug('Completed jsonld compact processing', compacted); 226 | compactDefer.resolve(compacted); 227 | } 228 | $rootScope.$apply(); 229 | }); 230 | return compactDefer.promise; 231 | } 232 | 233 | function isJsonld(response) { 234 | return response.headers('Content-Type') === 'application/ld+json'; 235 | } 236 | 237 | function isTypeCoercionProperty(context, prop, node){ 238 | return context[prop]['@type'] === '@id' && node[prop] !== undefined; 239 | } 240 | } 241 | 242 | })(); 243 | 244 | // Source: src/vocabularies/hydraCore.module.js 245 | (function() { 246 | var context = { 247 | '@context': { 248 | 'hydra': 'http://www.w3.org/ns/hydra/core#', 249 | 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 250 | 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#', 251 | 'xsd': 'http://www.w3.org/2001/XMLSchema#', 252 | 'owl': 'http://www.w3.org/2002/07/owl#', 253 | 'vs': 'http://www.w3.org/2003/06/sw-vocab-status/ns#', 254 | 'dc': 'http://purl.org/dc/terms/', 255 | 'cc': 'http://creativecommons.org/ns#', 256 | 'apiDocumentation': 'hydra:apiDocumentation', 257 | 'ApiDocumentation': 'hydra:ApiDocumentation', 258 | 'title': 'hydra:title', 259 | 'description': 'hydra:description', 260 | 'entrypoint': { '@id': 'hydra:entrypoint', '@type': '@id' }, 261 | 'supportedClass': { '@id': 'hydra:supportedClass', '@type': '@vocab' }, 262 | 'Class': 'hydra:Class', 263 | 'supportedProperty': { '@id': 'hydra:supportedProperty', '@type': '@id' }, 264 | 'SupportedProperty': 'hydra:SupportedProperty', 265 | 'property': { '@id': 'hydra:property', '@type': '@vocab' }, 266 | 'required': 'hydra:required', 267 | 'readonly': 'hydra:readonly', 268 | 'writeonly': 'hydra:writeonly', 269 | 'supportedOperation': { '@id': 'hydra:supportedOperation', '@type': '@id' }, 270 | 'Operation': 'hydra:Operation', 271 | 'CreateResourceOperation': 'hydra:CreateResourceOperation', 272 | 'ReplaceResourceOperation': 'hydra:ReplaceResourceOperation', 273 | 'DeleteResourceOperation': 'hydra:DeleteResourceOperation', 274 | 'method': 'hydra:method', 275 | 'expects': { '@id': 'hydra:expects', '@type': '@vocab' }, 276 | 'returns': { '@id': 'hydra:returns', '@type': '@vocab' }, 277 | 'statusCodes': { '@id': 'hydra:statusCodes', '@type': '@id' }, 278 | 'StatusCodeDescription': 'hydra:StatusCodeDescription', 279 | 'statusCode': 'hydra:statusCode', 280 | 'Error': 'hydra:Error', 281 | 'Resource': 'hydra:Resource', 282 | 'operation': 'hydra:operation', 283 | 'Collection': 'hydra:Collection', 284 | 'member': { '@id': 'hydra:member', '@type': '@id' }, 285 | 'search': 'hydra:search', 286 | 'freetextQuery': 'hydra:freetextQuery', 287 | 'PagedCollection': 'hydra:PagedCollection', 288 | 'totalItems': 'hydra:totalItems', 289 | 'itemsPerPage': 'hydra:itemsPerPage', 290 | 'firstPage': { '@id': 'hydra:firstPage', '@type': '@id' }, 291 | 'lastPage': { '@id': 'hydra:lastPage', '@type': '@id' }, 292 | 'nextPage': { '@id': 'hydra:nextPage', '@type': '@id' }, 293 | 'previousPage': { '@id': 'hydra:previousPage', '@type': '@id' }, 294 | 'Link': 'hydra:Link', 295 | 'TemplatedLink': 'hydra:TemplatedLink', 296 | 'IriTemplate': 'hydra:IriTemplate', 297 | 'template': 'hydra:template', 298 | 'mapping': 'hydra:mapping', 299 | 'IriTemplateMapping': 'hydra:IriTemplateMapping', 300 | 'variable': 'hydra:variable', 301 | 'defines': { '@reverse': 'rdfs:isDefinedBy' }, 302 | 'comment': 'rdfs:comment', 303 | 'label': 'rdfs:label', 304 | 'preferredPrefix': 'http://purl.org/vocab/vann/preferredNamespacePrefix', 305 | 'cc:license': {'@type': '@id' }, 306 | 'cc:attributionURL': {'@type': '@id' }, 307 | 'domain': { '@id': 'rdfs:domain', '@type': '@vocab' }, 308 | 'range': {'@id': 'rdfs:range', '@type': '@vocab' }, 309 | 'subClassOf': { '@id': 'rdfs:subClassOf', '@type': '@vocab' }, 310 | 'subPropertyOf': { '@id': 'rdfs:subPropertyOf', '@type': '@vocab' }, 311 | 'seeAlso': { '@id': 'rdfs:seeAlso', '@type': '@id' } 312 | } 313 | }; 314 | 315 | angular 316 | .module('angularJsonld.hydraCore', ['angularJsonld']) 317 | .config(config); 318 | 319 | /* @ngInject */ 320 | function config(jsonldProvider, jsonldContextProvider){ 321 | jsonldProvider.registerContext('http://www.w3.org/ns/hydra/context.jsonld', context); 322 | jsonldContextProvider.add({ 323 | 'hydra': 'http://www.w3.org/ns/hydra/core#' 324 | }); 325 | } 326 | 327 | })(); 328 | 329 | // Source: src/vocabularies/schema.org.module.js 330 | (function() { 331 | var context = { 332 | '@context': { 333 | '@vocab': 'http://schema.org/', 334 | 'acceptsReservations': { '@type': '@id' }, 335 | 'additionalType': { '@type': '@id' }, 336 | 'applicationCategory': { '@type': '@id' }, 337 | 'applicationSubCategory': { '@type': '@id' }, 338 | 'arrivalTime': { '@type': 'DateTime' }, 339 | 'artform': { '@type': '@id' }, 340 | 'availabilityEnds': { '@type': 'DateTime' }, 341 | 'availabilityStarts': { '@type': 'DateTime' }, 342 | 'availableFrom': { '@type': 'DateTime' }, 343 | 'availableThrough': { '@type': 'DateTime' }, 344 | 'birthDate': { '@type': 'Date' }, 345 | 'bookingTime': { '@type': 'DateTime' }, 346 | 'checkinTime': { '@type': 'DateTime' }, 347 | 'checkoutTime': { '@type': 'DateTime' }, 348 | 'codeRepository': { '@type': '@id' }, 349 | 'commentTime': { '@type': 'Date' }, 350 | 'contentUrl': { '@type': '@id' }, 351 | 'dateCreated': { '@type': 'Date' }, 352 | 'dateIssued': { '@type': 'DateTime' }, 353 | 'dateModified': { '@type': 'Date' }, 354 | 'datePosted': { '@type': 'Date' }, 355 | 'datePublished': { '@type': 'Date' }, 356 | 'deathDate': { '@type': 'Date' }, 357 | 'departureTime': { '@type': 'DateTime' }, 358 | 'discussionUrl': { '@type': '@id' }, 359 | 'dissolutionDate': { '@type': 'Date' }, 360 | 'doorTime': { '@type': 'DateTime' }, 361 | 'downloadUrl': { '@type': '@id' }, 362 | 'dropoffTime': { '@type': 'DateTime' }, 363 | 'embedUrl': { '@type': '@id' }, 364 | 'endDate': { '@type': 'Date' }, 365 | 'endTime': { '@type': 'DateTime' }, 366 | 'expectedArrivalFrom': { '@type': 'DateTime' }, 367 | 'expectedArrivalUntil': { '@type': 'DateTime' }, 368 | 'expires': { '@type': 'Date' }, 369 | 'featureList': { '@type': '@id' }, 370 | 'foundingDate': { '@type': 'Date' }, 371 | 'gameLocation': { '@type': '@id' }, 372 | 'gamePlatform': { '@type': '@id' }, 373 | 'guidelineDate': { '@type': 'Date' }, 374 | 'hasMap': { '@type': '@id' }, 375 | 'image': { '@type': '@id' }, 376 | 'installUrl': { '@type': '@id' }, 377 | 'isBasedOnUrl': { '@type': '@id' }, 378 | 'labelDetails': { '@type': '@id' }, 379 | 'lastReviewed': { '@type': 'Date' }, 380 | 'license': { '@type': '@id' }, 381 | 'logo': { '@type': '@id' }, 382 | 'map': { '@type': '@id' }, 383 | 'maps': { '@type': '@id' }, 384 | 'material': { '@type': '@id' }, 385 | 'memoryRequirements': { '@type': '@id' }, 386 | 'menu': { '@type': '@id' }, 387 | 'modifiedTime': { '@type': 'DateTime' }, 388 | 'namedPosition': { '@type': '@id' }, 389 | 'orderDate': { '@type': 'DateTime' }, 390 | 'ownedFrom': { '@type': 'DateTime' }, 391 | 'ownedThrough': { '@type': 'DateTime' }, 392 | 'paymentDue': { '@type': 'DateTime' }, 393 | 'paymentUrl': { '@type': '@id' }, 394 | 'pickupTime': { '@type': 'DateTime' }, 395 | 'prescribingInfo': { '@type': '@id' }, 396 | 'previousStartDate': { '@type': 'Date' }, 397 | 'priceValidUntil': { '@type': 'Date' }, 398 | 'publishingPrinciples': { '@type': '@id' }, 399 | 'relatedLink': { '@type': '@id' }, 400 | 'releaseDate': { '@type': 'Date' }, 401 | 'releaseNotes': { '@type': '@id' }, 402 | 'replyToUrl': { '@type': '@id' }, 403 | 'requirements': { '@type': '@id' }, 404 | 'roleName': { '@type': '@id' }, 405 | 'sameAs': { '@type': '@id' }, 406 | 'scheduledPaymentDate': { '@type': 'Date' }, 407 | 'scheduledTime': { '@type': 'DateTime' }, 408 | 'screenshot': { '@type': '@id' }, 409 | 'serviceUrl': { '@type': '@id' }, 410 | 'significantLink': { '@type': '@id' }, 411 | 'significantLinks': { '@type': '@id' }, 412 | 'sport': { '@type': '@id' }, 413 | 'startDate': { '@type': 'Date' }, 414 | 'startTime': { '@type': 'DateTime' }, 415 | 'storageRequirements': { '@type': '@id' }, 416 | 'surface': { '@type': '@id' }, 417 | 'targetUrl': { '@type': '@id' }, 418 | 'temporal': { '@type': 'DateTime' }, 419 | 'thumbnailUrl': { '@type': '@id' }, 420 | 'ticketToken': { '@type': '@id' }, 421 | 'trackingUrl': { '@type': '@id' }, 422 | 'uploadDate': { '@type': 'Date' }, 423 | 'url': { '@type': '@id' }, 424 | 'validFrom': { '@type': 'DateTime' }, 425 | 'validThrough': { '@type': 'DateTime' }, 426 | 'validUntil': { '@type': 'Date' }, 427 | 'warning': { '@type': '@id' }, 428 | 'webCheckinTime': { '@type': 'DateTime' } 429 | } 430 | }; 431 | 432 | angular 433 | .module('angularJsonld.schema.org', ['angularJsonld']) 434 | .config(config); 435 | 436 | /* @ngInject */ 437 | function config(jsonldProvider, jsonldContextProvider){ 438 | jsonldProvider.registerContext('http://schema.org', context); 439 | jsonldContextProvider.add({ 440 | 'schema': 'http://schema.org/' 441 | }); 442 | } 443 | 444 | })(); 445 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-jsonld", 3 | "version": "0.0.1", 4 | "dependencies": {}, 5 | "devDependencies": { 6 | "grunt": "^0.4.1", 7 | "grunt-autoprefixer": "^0.7.3", 8 | "grunt-concurrent": "^0.5.0", 9 | "grunt-contrib-clean": "^0.5.0", 10 | "grunt-contrib-concat": "^0.4.0", 11 | "grunt-contrib-connect": "^0.7.1", 12 | "grunt-contrib-copy": "^0.5.0", 13 | "grunt-contrib-cssmin": "^0.9.0", 14 | "grunt-contrib-htmlmin": "^0.3.0", 15 | "grunt-contrib-imagemin": "^0.7.0", 16 | "grunt-contrib-jshint": "^0.10.0", 17 | "grunt-contrib-uglify": "^0.4.0", 18 | "grunt-contrib-watch": "^0.6.1", 19 | "grunt-filerev": "^0.2.1", 20 | "grunt-google-cdn": "^0.4.0", 21 | "grunt-karma": "^0.9.0", 22 | "grunt-newer": "^0.7.0", 23 | "grunt-ng-annotate": "^0.9.2", 24 | "grunt-svgmin": "^0.4.0", 25 | "grunt-usemin": "^2.1.1", 26 | "grunt-wiredep": "^1.7.0", 27 | "jshint-stylish": "^0.2.0", 28 | "karma": "^0.12.24", 29 | "karma-jasmine": "^0.2.3", 30 | "karma-phantomjs-launcher": "^0.1.4", 31 | "load-grunt-tasks": "^0.4.0", 32 | "time-grunt": "^0.3.1" 33 | }, 34 | "engines": { 35 | "node": ">=0.10.0" 36 | }, 37 | "scripts": { 38 | "test": "grunt test" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/angularJsonld.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('angularJsonld', ['restangular']); 6 | 7 | })(); 8 | -------------------------------------------------------------------------------- /src/providers/jsonld.provider.js: -------------------------------------------------------------------------------- 1 | /* global jsonld */ 2 | (function(jsonld) { 3 | 'use strict'; 4 | angular 5 | .module('angularJsonld') 6 | .provider('jsonld', JsonldProvider); 7 | 8 | /* @ngInject */ 9 | function JsonldProvider() { 10 | var knownContexts = {}; 11 | var provider = this; 12 | 13 | /* @ngInject */ 14 | provider.$get = function($log) { 15 | var nodeDocumentLoader = jsonld.documentLoaders.xhr(); 16 | 17 | var customLoader = function(uri, callback) { 18 | if(uri in knownContexts) { 19 | $log.debug('Returning known context:', knownContexts[uri]); 20 | return callback( 21 | null, { 22 | contextUrl: null, // this is for a context via a link header 23 | document: knownContexts[uri], // this is the actual document that was loaded 24 | documentUrl: uri // this is the actual context URL after redirects 25 | }); 26 | } 27 | nodeDocumentLoader(uri).then(function(response){ 28 | callback(null, response); 29 | }). 30 | catch(function(err){ 31 | callback(err, null); 32 | }); 33 | }; 34 | jsonld.documentLoader = customLoader; 35 | return jsonld; 36 | }; 37 | 38 | provider.registerContext = function(uri, context) { 39 | knownContexts[uri] = context; 40 | }; 41 | } 42 | 43 | })(jsonld); 44 | -------------------------------------------------------------------------------- /src/providers/jsonldContext.provider.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * @name angularJsonld.contextProvider 6 | * @description 7 | * Provider to configure JSONLD context 8 | */ 9 | angular 10 | .module('angularJsonld') 11 | .provider('jsonldContext', JsonldContextProvider); 12 | 13 | /* @ngInject */ 14 | function JsonldContextProvider(){ 15 | var provider = this; 16 | var context = {}; 17 | 18 | provider.$get = function() { 19 | return Object.freeze(context); 20 | }; 21 | 22 | provider.add = function(c){ 23 | angular.extend(context, c); 24 | }; 25 | 26 | } 27 | 28 | })(); 29 | -------------------------------------------------------------------------------- /src/services/jsonldRest.service.js: -------------------------------------------------------------------------------- 1 | /* global */ 2 | (function() { 3 | 'use strict'; 4 | angular 5 | .module('angularJsonld') 6 | .factory('JsonldRest', JsonldRest); 7 | 8 | /* @ngInject */ 9 | function JsonldRest($q, $log, $rootScope, Restangular, jsonld, jsonldContext) { 10 | function JsonldRestangular(context) { 11 | var configuredRestangular = Restangular.withConfig(function(RestangularConfigurer){ 12 | RestangularConfigurer.setRestangularFields({ 13 | selfLink: '@id', 14 | get: '_get' 15 | }); 16 | RestangularConfigurer.setOnElemRestangularized(function(elem, isCollection, what, Restangular){ 17 | return angular.extend(elem,{ 18 | get: jsonldGet(elem, context, isCollection) 19 | }); 20 | }); 21 | }); 22 | 23 | var withConfigFn = configuredRestangular.withConfig; 24 | 25 | return angular.extend(configuredRestangular, { 26 | collection: collection, 27 | resource: resource, 28 | withConfig: function(f){ 29 | return new JsonldRest($q, $log, $rootScope, withConfigFn(f), jsonld, jsonldContext); 30 | }, 31 | withContext: function(c) { 32 | return new JsonldRestangular(c); 33 | } 34 | }); 35 | } 36 | 37 | var restangular = new JsonldRestangular(); 38 | 39 | return restangular; 40 | 41 | function resource(containerRoute, localRoute, context){ 42 | var ra = context? restangular.withContext(context): restangular; 43 | var r = ra.one(containerRoute, localRoute); 44 | return angular.extend(r, { 45 | withContext: function(c) { 46 | return resource(containerRoute, localRoute, c); 47 | } 48 | }); 49 | } 50 | 51 | function collection(route, context){ 52 | var ra = context? restangular.withContext(context): restangular; 53 | 54 | var col = ra.all(route); 55 | 56 | return angular.extend(col,{ 57 | withContext: function(c) { 58 | return collection(route, c); 59 | }, 60 | one: function(elementRoute){ 61 | return resource(route, elementRoute, context); 62 | }, 63 | getList: function(){ 64 | var members = arguments.length > 0? arguments[0] : '@graph'; 65 | var args = arguments.length > 1? Array.prototype.slice.apply(arguments, [1]) : undefined; 66 | return col.get.apply(col, args).then(function(res){ 67 | return angular.extend(asArray(res[members]), res); 68 | }); 69 | } 70 | }); 71 | } 72 | 73 | function asArray(obj){ 74 | if(obj instanceof Array) { 75 | return obj; 76 | } 77 | else { 78 | return [obj]; 79 | } 80 | } 81 | 82 | function restangularize(node, parent){ 83 | if(node instanceof Array){ 84 | return node.map(function(element){ 85 | return restangularize(element, parent); 86 | }); 87 | } 88 | for(var field in node) { 89 | if(node.hasOwnProperty(field) && field !== '@context' && typeof(node[field]) === 'object'){ 90 | node[field] = restangularize(node[field], node); 91 | } 92 | } 93 | if(node['@context']){ 94 | var context = node['@context']; 95 | for(var prop in context){ 96 | if(context.hasOwnProperty(prop) && isTypeCoercionProperty(context, prop, node)){ 97 | node[prop] = restangularize({'@id':node[prop]}, node); 98 | } 99 | } 100 | } 101 | 102 | if(node['@id']) { 103 | var link = restangular.restangularizeElement(parent, node, node['@id']); 104 | $log.info('Created Restangular subresource: ', link); 105 | return angular.extend(link, { 106 | get: jsonldGet(link) 107 | }); 108 | } 109 | else { 110 | return node; 111 | } 112 | } 113 | 114 | function jsonldGet(obj, context, isCollection){ 115 | if(angular.isFunction(obj._get)){ 116 | var doGet = function(params){ 117 | if(isCollection) { 118 | return obj._get('', params); 119 | } 120 | return obj._get(params); 121 | }; 122 | return function(params) { 123 | return doGet(params).then(function(data){ 124 | return compact(data, context); 125 | }).then(function(compacted){ 126 | return restangularize(compacted); 127 | }); 128 | }; 129 | } 130 | return undefined; 131 | } 132 | 133 | function compact(data, context){ 134 | var c = angular.copy(jsonldContext); 135 | if(context){ 136 | angular.extend(c, context); 137 | } 138 | var compactDefer = $q.defer(); 139 | jsonld.compact(data, c, function(err, compacted){ 140 | if(err) { 141 | $log.error('Faild compact jsonld', err); 142 | compactDefer.reject(err); 143 | } 144 | else { 145 | $log.debug('Completed jsonld compact processing', compacted); 146 | compactDefer.resolve(compacted); 147 | } 148 | $rootScope.$apply(); 149 | }); 150 | return compactDefer.promise; 151 | } 152 | 153 | function isJsonld(response) { 154 | return response.headers('Content-Type') === 'application/ld+json'; 155 | } 156 | 157 | function isTypeCoercionProperty(context, prop, node){ 158 | return context[prop]['@type'] === '@id' && node[prop] !== undefined; 159 | } 160 | } 161 | 162 | })(); 163 | -------------------------------------------------------------------------------- /src/vocabularies/hydraCore.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | var context = { 5 | '@context': { 6 | 'hydra': 'http://www.w3.org/ns/hydra/core#', 7 | 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 8 | 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#', 9 | 'xsd': 'http://www.w3.org/2001/XMLSchema#', 10 | 'owl': 'http://www.w3.org/2002/07/owl#', 11 | 'vs': 'http://www.w3.org/2003/06/sw-vocab-status/ns#', 12 | 'dc': 'http://purl.org/dc/terms/', 13 | 'cc': 'http://creativecommons.org/ns#', 14 | 'apiDocumentation': 'hydra:apiDocumentation', 15 | 'ApiDocumentation': 'hydra:ApiDocumentation', 16 | 'title': 'hydra:title', 17 | 'description': 'hydra:description', 18 | 'entrypoint': { '@id': 'hydra:entrypoint', '@type': '@id' }, 19 | 'supportedClass': { '@id': 'hydra:supportedClass', '@type': '@vocab' }, 20 | 'Class': 'hydra:Class', 21 | 'supportedProperty': { '@id': 'hydra:supportedProperty', '@type': '@id' }, 22 | 'SupportedProperty': 'hydra:SupportedProperty', 23 | 'property': { '@id': 'hydra:property', '@type': '@vocab' }, 24 | 'required': 'hydra:required', 25 | 'readonly': 'hydra:readonly', 26 | 'writeonly': 'hydra:writeonly', 27 | 'supportedOperation': { '@id': 'hydra:supportedOperation', '@type': '@id' }, 28 | 'Operation': 'hydra:Operation', 29 | 'CreateResourceOperation': 'hydra:CreateResourceOperation', 30 | 'ReplaceResourceOperation': 'hydra:ReplaceResourceOperation', 31 | 'DeleteResourceOperation': 'hydra:DeleteResourceOperation', 32 | 'method': 'hydra:method', 33 | 'expects': { '@id': 'hydra:expects', '@type': '@vocab' }, 34 | 'returns': { '@id': 'hydra:returns', '@type': '@vocab' }, 35 | 'statusCodes': { '@id': 'hydra:statusCodes', '@type': '@id' }, 36 | 'StatusCodeDescription': 'hydra:StatusCodeDescription', 37 | 'statusCode': 'hydra:statusCode', 38 | 'Error': 'hydra:Error', 39 | 'Resource': 'hydra:Resource', 40 | 'operation': 'hydra:operation', 41 | 'Collection': 'hydra:Collection', 42 | 'member': { '@id': 'hydra:member', '@type': '@id' }, 43 | 'search': 'hydra:search', 44 | 'freetextQuery': 'hydra:freetextQuery', 45 | 'PagedCollection': 'hydra:PagedCollection', 46 | 'totalItems': 'hydra:totalItems', 47 | 'itemsPerPage': 'hydra:itemsPerPage', 48 | 'firstPage': { '@id': 'hydra:firstPage', '@type': '@id' }, 49 | 'lastPage': { '@id': 'hydra:lastPage', '@type': '@id' }, 50 | 'nextPage': { '@id': 'hydra:nextPage', '@type': '@id' }, 51 | 'previousPage': { '@id': 'hydra:previousPage', '@type': '@id' }, 52 | 'Link': 'hydra:Link', 53 | 'TemplatedLink': 'hydra:TemplatedLink', 54 | 'IriTemplate': 'hydra:IriTemplate', 55 | 'template': 'hydra:template', 56 | 'mapping': 'hydra:mapping', 57 | 'IriTemplateMapping': 'hydra:IriTemplateMapping', 58 | 'variable': 'hydra:variable', 59 | 'defines': { '@reverse': 'rdfs:isDefinedBy' }, 60 | 'comment': 'rdfs:comment', 61 | 'label': 'rdfs:label', 62 | 'preferredPrefix': 'http://purl.org/vocab/vann/preferredNamespacePrefix', 63 | 'cc:license': {'@type': '@id' }, 64 | 'cc:attributionURL': {'@type': '@id' }, 65 | 'domain': { '@id': 'rdfs:domain', '@type': '@vocab' }, 66 | 'range': {'@id': 'rdfs:range', '@type': '@vocab' }, 67 | 'subClassOf': { '@id': 'rdfs:subClassOf', '@type': '@vocab' }, 68 | 'subPropertyOf': { '@id': 'rdfs:subPropertyOf', '@type': '@vocab' }, 69 | 'seeAlso': { '@id': 'rdfs:seeAlso', '@type': '@id' } 70 | } 71 | }; 72 | 73 | angular 74 | .module('angularJsonld.hydraCore', ['angularJsonld']) 75 | .config(config); 76 | 77 | /* @ngInject */ 78 | function config(jsonldProvider, jsonldContextProvider){ 79 | jsonldProvider.registerContext('http://www.w3.org/ns/hydra/context.jsonld', context); 80 | jsonldContextProvider.add({ 81 | 'hydra': 'http://www.w3.org/ns/hydra/core#' 82 | }); 83 | } 84 | 85 | })(); 86 | -------------------------------------------------------------------------------- /src/vocabularies/schema.org.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | var context = { 5 | '@context': { 6 | '@vocab': 'http://schema.org/', 7 | 'acceptsReservations': { '@type': '@id' }, 8 | 'additionalType': { '@type': '@id' }, 9 | 'applicationCategory': { '@type': '@id' }, 10 | 'applicationSubCategory': { '@type': '@id' }, 11 | 'arrivalTime': { '@type': 'DateTime' }, 12 | 'artform': { '@type': '@id' }, 13 | 'availabilityEnds': { '@type': 'DateTime' }, 14 | 'availabilityStarts': { '@type': 'DateTime' }, 15 | 'availableFrom': { '@type': 'DateTime' }, 16 | 'availableThrough': { '@type': 'DateTime' }, 17 | 'birthDate': { '@type': 'Date' }, 18 | 'bookingTime': { '@type': 'DateTime' }, 19 | 'checkinTime': { '@type': 'DateTime' }, 20 | 'checkoutTime': { '@type': 'DateTime' }, 21 | 'codeRepository': { '@type': '@id' }, 22 | 'commentTime': { '@type': 'Date' }, 23 | 'contentUrl': { '@type': '@id' }, 24 | 'dateCreated': { '@type': 'Date' }, 25 | 'dateIssued': { '@type': 'DateTime' }, 26 | 'dateModified': { '@type': 'Date' }, 27 | 'datePosted': { '@type': 'Date' }, 28 | 'datePublished': { '@type': 'Date' }, 29 | 'deathDate': { '@type': 'Date' }, 30 | 'departureTime': { '@type': 'DateTime' }, 31 | 'discussionUrl': { '@type': '@id' }, 32 | 'dissolutionDate': { '@type': 'Date' }, 33 | 'doorTime': { '@type': 'DateTime' }, 34 | 'downloadUrl': { '@type': '@id' }, 35 | 'dropoffTime': { '@type': 'DateTime' }, 36 | 'embedUrl': { '@type': '@id' }, 37 | 'endDate': { '@type': 'Date' }, 38 | 'endTime': { '@type': 'DateTime' }, 39 | 'expectedArrivalFrom': { '@type': 'DateTime' }, 40 | 'expectedArrivalUntil': { '@type': 'DateTime' }, 41 | 'expires': { '@type': 'Date' }, 42 | 'featureList': { '@type': '@id' }, 43 | 'foundingDate': { '@type': 'Date' }, 44 | 'gameLocation': { '@type': '@id' }, 45 | 'gamePlatform': { '@type': '@id' }, 46 | 'guidelineDate': { '@type': 'Date' }, 47 | 'hasMap': { '@type': '@id' }, 48 | 'image': { '@type': '@id' }, 49 | 'installUrl': { '@type': '@id' }, 50 | 'isBasedOnUrl': { '@type': '@id' }, 51 | 'labelDetails': { '@type': '@id' }, 52 | 'lastReviewed': { '@type': 'Date' }, 53 | 'license': { '@type': '@id' }, 54 | 'logo': { '@type': '@id' }, 55 | 'map': { '@type': '@id' }, 56 | 'maps': { '@type': '@id' }, 57 | 'material': { '@type': '@id' }, 58 | 'memoryRequirements': { '@type': '@id' }, 59 | 'menu': { '@type': '@id' }, 60 | 'modifiedTime': { '@type': 'DateTime' }, 61 | 'namedPosition': { '@type': '@id' }, 62 | 'orderDate': { '@type': 'DateTime' }, 63 | 'ownedFrom': { '@type': 'DateTime' }, 64 | 'ownedThrough': { '@type': 'DateTime' }, 65 | 'paymentDue': { '@type': 'DateTime' }, 66 | 'paymentUrl': { '@type': '@id' }, 67 | 'pickupTime': { '@type': 'DateTime' }, 68 | 'prescribingInfo': { '@type': '@id' }, 69 | 'previousStartDate': { '@type': 'Date' }, 70 | 'priceValidUntil': { '@type': 'Date' }, 71 | 'publishingPrinciples': { '@type': '@id' }, 72 | 'relatedLink': { '@type': '@id' }, 73 | 'releaseDate': { '@type': 'Date' }, 74 | 'releaseNotes': { '@type': '@id' }, 75 | 'replyToUrl': { '@type': '@id' }, 76 | 'requirements': { '@type': '@id' }, 77 | 'roleName': { '@type': '@id' }, 78 | 'sameAs': { '@type': '@id' }, 79 | 'scheduledPaymentDate': { '@type': 'Date' }, 80 | 'scheduledTime': { '@type': 'DateTime' }, 81 | 'screenshot': { '@type': '@id' }, 82 | 'serviceUrl': { '@type': '@id' }, 83 | 'significantLink': { '@type': '@id' }, 84 | 'significantLinks': { '@type': '@id' }, 85 | 'sport': { '@type': '@id' }, 86 | 'startDate': { '@type': 'Date' }, 87 | 'startTime': { '@type': 'DateTime' }, 88 | 'storageRequirements': { '@type': '@id' }, 89 | 'surface': { '@type': '@id' }, 90 | 'targetUrl': { '@type': '@id' }, 91 | 'temporal': { '@type': 'DateTime' }, 92 | 'thumbnailUrl': { '@type': '@id' }, 93 | 'ticketToken': { '@type': '@id' }, 94 | 'trackingUrl': { '@type': '@id' }, 95 | 'uploadDate': { '@type': 'Date' }, 96 | 'url': { '@type': '@id' }, 97 | 'validFrom': { '@type': 'DateTime' }, 98 | 'validThrough': { '@type': 'DateTime' }, 99 | 'validUntil': { '@type': 'Date' }, 100 | 'warning': { '@type': '@id' }, 101 | 'webCheckinTime': { '@type': 'DateTime' } 102 | } 103 | }; 104 | 105 | angular 106 | .module('angularJsonld.schema.org', ['angularJsonld']) 107 | .config(config); 108 | 109 | /* @ngInject */ 110 | function config(jsonldProvider, jsonldContextProvider){ 111 | jsonldProvider.registerContext('http://schema.org', context); 112 | jsonldContextProvider.add({ 113 | 'schema': 'http://schema.org/' 114 | }); 115 | } 116 | 117 | })(); 118 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "after": false, 23 | "afterEach": false, 24 | "angular": false, 25 | "before": false, 26 | "beforeEach": false, 27 | "browser": false, 28 | "describe": false, 29 | "expect": false, 30 | "inject": false, 31 | "it": false, 32 | "jasmine": false, 33 | "spyOn": false 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.12/config/configuration-file.html 3 | // Generated on 2014-11-08 using 4 | // generator-karma 0.8.3 5 | 6 | module.exports = function(config) { 7 | 'use strict'; 8 | 9 | config.set({ 10 | // enable / disable watching file and executing tests whenever any file changes 11 | autoWatch: true, 12 | 13 | // base path, that will be used to resolve files and exclude 14 | basePath: '../', 15 | 16 | // testing framework to use (jasmine/mocha/qunit/...) 17 | frameworks: ['jasmine'], 18 | 19 | // list of files / patterns to load in the browser 20 | files: [ 21 | 'bower_components/angular/angular.js', 22 | 'bower_components/angular-mocks/angular-mocks.js', 23 | 'bower_components/restangular/dist/restangular.js', 24 | 'bower_components/lodash/dist/lodash.compat.js', 25 | 'bower_components/jsonld/js/jsonld.js', 26 | 'src/*.module.js', 27 | 'src/**/*.js', 28 | 'test/mock/**/*.js', 29 | 'test/spec/**/*.js' 30 | ], 31 | 32 | // list of files / patterns to exclude 33 | exclude: [], 34 | 35 | // web server port 36 | port: 8080, 37 | 38 | // Start these browsers, currently available: 39 | // - Chrome 40 | // - ChromeCanary 41 | // - Firefox 42 | // - Opera 43 | // - Safari (only Mac) 44 | // - PhantomJS 45 | // - IE (only Windows) 46 | browsers: [ 47 | 'PhantomJS' 48 | ], 49 | 50 | // Which plugins to enable 51 | plugins: [ 52 | 'karma-phantomjs-launcher', 53 | 'karma-jasmine' 54 | ], 55 | 56 | // Continuous Integration mode 57 | // if true, it capture browsers, run tests and exit 58 | singleRun: false, 59 | 60 | colors: true, 61 | 62 | // level of logging 63 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 64 | logLevel: config.LOG_INFO, 65 | 66 | // Uncomment the following lines if you are using grunt's server to run the tests 67 | // proxies: { 68 | // '/': 'http://localhost:9000/' 69 | // }, 70 | // URL root prevent conflicts with the site root 71 | // urlRoot: '_karma_' 72 | client: { 73 | captureConsole: true 74 | } 75 | }); 76 | }; 77 | -------------------------------------------------------------------------------- /test/spec/angularJsonldSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Module angularJsonld', function() { 4 | 5 | var jsonld; 6 | var JsonldRest; 7 | 8 | // load the service's module 9 | beforeEach(module('angularJsonld', function($provide){ 10 | $provide.value('$log',console); 11 | })); 12 | 13 | beforeEach(inject(function($injector) { 14 | jsonld = $injector.get('jsonld'); 15 | JsonldRest = $injector.get('JsonldRest'); 16 | })); 17 | 18 | it('should provide jsonld.js library', function(){ 19 | expect(jsonld).toBeDefined(); 20 | }); 21 | 22 | it('should provide the jsonldRest service', function(){ 23 | expect(JsonldRest).toBeDefined(); 24 | }); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /test/spec/services/jsonldRestSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('jsonldRest', function() { 4 | 5 | var $httpBackend; 6 | var $timeout; 7 | var $rootScope; 8 | var JsonldRest; 9 | 10 | var sampleHydraCollection = { 11 | '@context': 'http://www.w3.org/ns/hydra/context.jsonld', 12 | '@type': 'hydra:PagedCollection', 13 | 'member': [ 14 | {'@id':'/sites/1','@type':'Site','lang':'es'}, 15 | {'@id':'/sites/2','@type':'Site','lang':'en'} 16 | ], 17 | 'firstPage': 'http://example.org/collection?page=0', 18 | '@id': 'http://example.org/thisColId' 19 | }; 20 | 21 | // load the service's module 22 | beforeEach(module('angularJsonld', function($provide){ 23 | $provide.value('$log',console); 24 | })); 25 | beforeEach(module('angularJsonld.hydraCore', function($provide){ 26 | $provide.value('$log',console); 27 | })); 28 | 29 | beforeEach(inject(function($injector) { 30 | $httpBackend = $injector.get('$httpBackend'); 31 | $timeout = $injector.get('$timeout'); 32 | $rootScope = $injector.get('$rootScope'); 33 | JsonldRest = $injector.get('JsonldRest'); 34 | })); 35 | 36 | afterEach(function(){ 37 | //$httpBackend.verifyNoOutstandingExpectation(); 38 | //$httpBackend.verifyNoOutstandingRequest(); 39 | }); 40 | 41 | it('should compact and provide extended context', function(done){ 42 | $httpBackend.expectGET('/collection').respond(sampleHydraCollection, {'Content-Type': 'application/ld+json'}); 43 | JsonldRest.collection('collection').get().then(function(data){ 44 | expect(data['hydra:firstPage']['@id']).toEqual('http://example.org/collection?page=0'); 45 | }). 46 | catch(function(err){ 47 | throw(err); 48 | }). 49 | finally(done); 50 | $rootScope.$apply(); 51 | $httpBackend.flush(); 52 | }); 53 | 54 | it('should make links navigatables', function(done){ 55 | $httpBackend.expectGET('/collection').respond(sampleHydraCollection, {'Content-Type': 'application/ld+json'}); 56 | $httpBackend.whenGET('http://example.org/collection?page=0').respond({}); 57 | JsonldRest.collection('collection').get().then(function(data){ 58 | data['hydra:firstPage'].get(); 59 | }). 60 | catch(function(err){ 61 | throw(err); 62 | }). 63 | finally(function(){ 64 | done(); 65 | }); 66 | $rootScope.$apply(); 67 | $httpBackend.flush(); 68 | }); 69 | 70 | it('should support local context', function(done){ 71 | $httpBackend.expectGET('/collection').respond(sampleHydraCollection, {'Content-Type': 'application/ld+json'}); 72 | JsonldRest.collection('collection').withContext({'fp': 'hydra:firstPage'}).get().then(function(data){ 73 | expect(data.fp['@id']).toEqual('http://example.org/collection?page=0'); 74 | expect(data.fp.get).toBeDefined(); 75 | }). 76 | catch(function(err){ 77 | throw(err); 78 | }). 79 | finally(done); 80 | $rootScope.$apply(); 81 | $httpBackend.flush(); 82 | }); 83 | 84 | it('should return a JsonldRest when call withConfig', function(){ 85 | var client = JsonldRest.withConfig(function(configurator){ 86 | configurator.setRestangularFields({ 87 | selfLink: '@id' 88 | }); 89 | }); 90 | expect(client.collection).toBeDefined(); 91 | }); 92 | 93 | it('should acccess resource', function(done){ 94 | $httpBackend.expectGET('/collection/res').respond(sampleHydraCollection, {'Content-Type': 'application/ld+json'}); 95 | JsonldRest.resource('collection', 'res').get().then(function(data){ 96 | expect(data['@id']).toEqual('http://example.org/thisColId'); 97 | }). 98 | catch(function(err){ 99 | throw(err); 100 | }). 101 | finally(done); 102 | 103 | $httpBackend.flush(); 104 | $rootScope.$apply(); 105 | }); 106 | 107 | it('should restangularize fields with type coercion', function(done){ 108 | $httpBackend.expectGET('/collection/res').respond({ 109 | '@context': { 110 | 'p': {'@id': 'http://example/p', '@type': '@id'} 111 | }, 112 | p: 'http://example.org/a' 113 | }, {'Content-Type': 'application/ld+json'}); 114 | 115 | JsonldRest.resource('collection', 'res').withContext({ 116 | p: 'http://example/p' 117 | }).get().then(function(data){ 118 | expect(data.p.get).toBeDefined(); 119 | }). 120 | catch(function(err){ 121 | throw(err); 122 | }). 123 | finally(done); 124 | $rootScope.$apply(); 125 | $httpBackend.flush(); 126 | 127 | }); 128 | 129 | it('should allow passing query parameters when getting a collection', function(){ 130 | $httpBackend.expectGET('/collection?p1=a').respond({}, {'Content-Type': 'application/ld+json'}); 131 | JsonldRest.collection('collection').get({p1:'a'}); 132 | 133 | $rootScope.$apply(); 134 | $httpBackend.flush(); 135 | }); 136 | 137 | it('should return resources with modified get', function(done){ 138 | $httpBackend.expectGET('/collection/123').respond(sampleHydraCollection, {'Content-Type': 'application/ld+json'}); 139 | var resource = JsonldRest.resource('collection', '123'); 140 | resource.get().then(function(res){ 141 | expect(res['hydra:firstPage']).toBeDefined(); 142 | }). 143 | catch(function(err){ 144 | throw(err); 145 | }). 146 | finally(done); 147 | $rootScope.$apply(); 148 | $httpBackend.flush(); 149 | }); 150 | 151 | it('getList(member) should return an array of jsonld objects', function(done){ 152 | $httpBackend.expectGET('/collection').respond(sampleHydraCollection, {'Content-Type': 'application/ld+json'}); 153 | var col = JsonldRest.collection('collection'); 154 | col.getList('hydra:member').then(function(res){ 155 | expect(res.length).toBe(2); 156 | expect(res[0]['@id']).toBe('/sites/1'); 157 | expect(res['hydra:firstPage']).toBeDefined(); 158 | }). 159 | catch(function(err){ 160 | throw(err); 161 | }). 162 | finally(done); 163 | $rootScope.$apply(); 164 | $httpBackend.flush(); 165 | }); 166 | 167 | it('getList(member) should support request arguments', function(done){ 168 | $httpBackend.expectGET('/collection?q=hello').respond(sampleHydraCollection, {'Content-Type': 'application/ld+json'}); 169 | var col = JsonldRest.collection('collection'); 170 | col.getList('hydra:member', {q: 'hello'}).then(function(res){ 171 | expect(res.length).toBe(2); 172 | }). 173 | catch(function(err){ 174 | throw(err); 175 | }). 176 | finally(done); 177 | $rootScope.$apply(); 178 | $httpBackend.flush(); 179 | }); 180 | 181 | }); 182 | --------------------------------------------------------------------------------