diff --git a/.gitignore b/.gitignore index 58bcbf8..0a70862 100644 --- a/.gitignore +++ b/.gitignore @@ -1,32 +1,33 @@ -# Windows image file caches -Thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msm -*.msp - -# ========================= -# Operating System Files -# ========================= - -# OSX -# ========================= - +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# ========================= +# Operating System Files +# ========================= + +# OSX +# ========================= + .DS_Store .AppleDouble .LSOverride # Icon must end with two \r -Icon +Icon + # Thumbnails ._* @@ -41,3 +42,6 @@ Icon Network Trash Folder Temporary Items .apdisk + +#nodemodules +node_modules diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6160d82 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: node_js +node_js: + - "4.1" + - "4.0" + - "0.12" + - "0.11" + - "0.10" + - "iojs" \ No newline at end of file diff --git a/README.md b/README.md index e56cf0f..b216400 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # o.js -*o.js beta v0.1.1* +*o.js beta v0.2.0* -o.js is a client side Odata Javascript library to simplify the request of data. The main goal is to build a **standalone, lightweight and easy** to understand Odata lib. +o.js is a client side Odata Javascript library to simplify the request of data. The main goal is to build a **standalone, lightweight and easy** to understand Odata lib. ## Install ## ------------ @@ -11,9 +11,16 @@ Download the *o.js* or *o.min.js* file or install it via *bower* and add the scr bower install o.js ``` +You can use o.js in node.js as well: +``` + npm install o.js + + //var o = require('o.js'); +``` + ## Samples ## ------------ -For alle samples we are using the test odata service from Odata.org. You can find the metadata of this service here. +For alle samples we are using the test odata service from Odata.org. You can find the metadata of this service here. ### Simple Odata query with o.js ### ---------------------- @@ -21,7 +28,7 @@ For alle samples we are using the test odata service from See this demonstration for more examples. - +Instead of manual getting your data with the `get()` function, this routing function always returns the data when somebody navigates to an URL with the hash value `index.html#Product/Detail/1/Some more parameter`. The `find()` method automatically maps the right parameter (in this example *1*). See this demonstration for more examples. + ### Get data (details) ### -------- -If you want to get data you need to call the `get()` function. This functions returns an async callback function which holds an array as it's parameter. If you use `first()` or the `find()` method it only returns the data because an array is not needed. +If you want to get data you need to call the `get()` function. This functions returns an async callback function which holds an array as it's parameter. If you use `first()` or the `find()` method it only returns the data because an array is not needed. You can also save your o-function to call it later: ```js var oHandler=o('http://services.odata.org/V4/OData/OData.svc/Products'); @@ -60,7 +67,7 @@ oHandler.get(function(data) { //or the saved var also contains the data: console.log(oHandler.data); }); -``` +``` If you need to get several data you can use promise. Currently o.js only suports [q.js](https://github.com/kriskowal/q). The following example show how you can get the data of two differend resources: ```js @@ -81,7 +88,7 @@ o('http://services.odata.org/V4/OData/OData.svc/Products(2)').get().then(functio }).fail(function(ex) { console.log(ex); } -``` +``` ## Add and change data ## --------- @@ -94,34 +101,34 @@ You can use the `post()` function in combination with the `save()` method to add o('http://services.odata.org/V4/OData/OData.svc/Products').post({Name:'Example 1',Description:'a'}).post({Name:'Example 2',Description:'b'}).save(function(data) { console.log("Two Products added"); } -```` +```` ### Patch/Put: ### --------- Changing (PATCH or PUT) data is nearly the same: ```js o('http://services.odata.org/V4/OData/OData.svc/Products(1)').patch({Name:'NewName'}).save(function(data) { - console.log("Product Name changed"); + console.log("Product Name changed"); }); -```` +```` ### Delete: ### --------- To remove (DELETE) data you need to call `remove()`: ```js o('http://services.odata.org/V4/OData/OData.svc/Products(1)').remove().save(function(data) { - console.log("Product deleted"); + console.log("Product deleted"); }); -```` +```` ### Reference: ### --------- To add an reference to an other resource use `ref` (to remove it simply use `removeRef` the same way): ```js o('http://services.odata.org/V4/OData/OData.svc/Products(1)').ref('Categories', 2).save(function(data) { - console.log("Product(1) associated with Categories(2)"); + console.log("Product(1) associated with Categories(2)"); }); -```` +```` You can also combine a single data request (`first()` or `find()`) with the save method and chain it: ```js @@ -133,7 +140,7 @@ o('http://services.odata.org/V4/OData/OData.svc/Products').find(2).get().then(fu }).fail(function(ex) { console.log("error"); }); -``` +``` ### Endpoint configuration ### --------- @@ -143,7 +150,7 @@ You can configure a endpoint with the `o().config()` function. This config is pe o().config({ endpoint:'http://services.odata.org/V4/OData/OData.svc' }); - + // after you have set an endpoint, you can shorten your queries: o('Products').get(function(data) { //same result like the first exmple on this page @@ -165,7 +172,7 @@ However, if you have set an endpoint you can still do a full endpoint request fo password:null, // a basic auth password isAsync:true //set this to false to make synced (a)jax calls. (dosn't work with basic auth!) }); -``` +``` ### Full list of supported functions ### --------- @@ -173,39 +180,39 @@ However, if you have set an endpoint you can still do a full endpoint request fo Currently the following queries are supported: `.find(int)` - returns the object with the given id. (Odata: Products*(1)*) - + `.top(int)` - returns the top x objects (Odata: Products/?*$top=2*) - Synonym: `.take` - + `.skip(int)` - returns the objects skipped by the given value (Odata: Products/?*$skip=2*) - + `.first()` - returns the first object which is found (Odata: Products/?*$top=1*) - + `.filter(string)` - adds a filter string (o.js can convered simple JS-Syntax. If you need something complex use the plain Odata $filter syntax: [see the Odata doc](http://www.odata.org/documentation/odata-version-3-0/url-conventions/) for more information) (Odata: Products/?*$filter=Name eq 'Example'*) - Synonym: `.where` - + `.any(string, string)` - applies an any filter to an resource (Odata: Products/?*$filter=Categories/any(x:x/Name eq 'Test')*) - + `.search(array, string)` - builds up a search $filter. The first parameter defines the columns to search in the second the searchword (e.g.: `.search(['Name', 'Description'], 'Test')`) - + `.orderBy(string)` - orders the data (Odata: Products/?*$orderBy=Name*) - + `.orderByDesc(string)` - orders the data descading (Odata: Products/?*$orderBy=Name*) - + `.count()` - only counts the result (Odata: Products/*$count*) - + `.inlineCount(string)` - adds a inlinecount to the result. (Odata: Products/?*$count=true*) - + `.batch(string)` - adds a second resource to the request (Odata: $batch) - + `.expand(string)` - expands a related resource (Odata: Products/?*$expand=ProductUnit*) - + `.ref(string, string)` - expands a related resource (Odata: Products/*$ref=Categories(1)*) - + `.deleteRef(string, string)` - expands a related resource (Odata: Products/*$ref=Categories(1)*) - + `.post(object)` - Post data to an endpoint - + `.patch(object)` - PATCH data on an endpoint - + `.put(object)` - PUT data on an endpoint - + `.remove(object)` - DELETE data on an endpoint (You must define only one resource: e.g: Products(1) ) diff --git a/example/app.js b/example/app.js index a59f3c0..8450b2f 100644 --- a/example/app.js +++ b/example/app.js @@ -3,14 +3,14 @@ // // An example for o.js. // -// By Jan Hommes +// By Jan Hommes // Date: 15.06.2015 // +++ //knockout view model function ViewModel() { var self=this; - + //ko observables self.People=ko.observableArray([]); //self.currentPeople=ko.observable(null); @@ -19,7 +19,7 @@ function ViewModel() { self.total=ko.observable(0); self.detailPeople=ko.observable(); self.isLoading=ko.observable(false); - + self.remove = function(d) { o('People(\'' + self.detailPeople().UserName + '\')/Trips(' + d.TripId + ')').remove().save(function() { o('People(\'' + self.detailPeople().UserName + '\')').expand('Trips').get(function(d) { @@ -27,7 +27,7 @@ function ViewModel() { }); }); } - + //o.js init o().config({ endpoint:'http://services.odata.org/V4/%28S%28wptr35qf3bz4kb5oatn432ul%29%29/TripPinServiceRW/', @@ -40,8 +40,8 @@ function ViewModel() { self.isLoading(false); } }); - - + + //+++ initialize the routes +++ //get top 3 People on start TODO: At filter for best selling! @@ -49,28 +49,26 @@ function ViewModel() { self.route('Home'); self.People(data); }).triggerRoute(window.location.hash === '' ? '#Home' : window.location.hash); - + //get a People list on People click - o('People').take(9).inlineCount().route('People',function(data) { + o('People').take(9).inlineCount().route('People', function(data) { self.route('People'); self.People(data); self.skip(0); self.total(this.inlinecount); }); - + //People pagination - o('People').skip(':0').take(9).inlineCount().route('People/Page/:0',function(data) { - console.log(this.param); + o('People').skip(':0').take(9).inlineCount().route('People/Page/:0', function(data) { self.skip(parseInt(this.param[0])); self.route('People'); self.People(data); self.total(this.inlinecount); }); - + //People detail - o('People').filter('UserName == \':0\'').expand('Trips').first().route('People/Detail/:0',function(data) { + o('People').filter('UserName == \':0\'').expand('Trips').first().route('People/Detail/:0', function(data) { self.route('Detail'); - console.log(data); self.detailPeople(data); }); } diff --git a/o.js b/o.js index 17aeeaf..3b36bf7 100644 --- a/o.js +++ b/o.js @@ -1,24 +1,24 @@ // +++ -// o.js v0.1b +// o.js v0.2.0 // // o.js is a simple oData wrapper for JavaScript. -// Currently supporting the following operations: +// Currently supporting the following operations: // .get() / .post() / .put() / .delete() / .first() / .take() / .skip() / .filter() / .orderBy() / .orderByDesc() / .count() /.search() / .select() / .any() / .ref() / .deleteRef() // -// By Jan Hommes -// Date: 25.06.2015 +// By Jan Hommes +// Date: 13.01.2016 // -------------------- // The MIT License (MIT) // -// Copyright (c) 2015 Jan Hommes -// +// Copyright (c) 2016 Jan Hommes +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. @@ -30,9 +30,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // +++ - function o(res) { - base = this; + var base = this; //base config object base.oConfig = base.oConfig || { @@ -107,7 +106,7 @@ function o(res) { function oData(res, config) { - base = this; + var base = this; // --------------------+++ VARIABLES +++--------------------------- @@ -141,7 +140,7 @@ function oData(res, config) { }; - //base external variables + //base external variables base.data = []; //holds the data after an callback base.inlinecount = null; //if inlinecount is set, here the counting is gold base.param = {}; //this object holds all parameter for a route @@ -160,6 +159,11 @@ function oData(res, config) { routes = [routes]; } + // check support + if(typeof window === 'undefined') { + throwEx('Routes are only supported in a browser env.'); + } + var prevHash = window.location.hash; //literate over every rout and add a interval to check if the route is triggered @@ -421,18 +425,20 @@ function oData(res, config) { // TODO: maybe add some pseudonyms... // +++ base.get = function (callback, errorCallback) { + // init the q -> if node require a node promise -> if ES6, try ES6 promise + var promise = initPromise(); + currentPromise = promise.defer(); + //start the request - if (typeof Q !== 'undefined') - currentPromise = Q.defer(); startRequest(callback, errorCallback, false); - if (typeof Q !== 'undefined') + if (typeof promise !== 'undefined') return (currentPromise.promise); else return (base); } // +++ - // adds a dataset to the current selected resource + // adds a dataset to the current selected resource // if o("Product/ProductGroup").post(...) will post a dataset to the Product resource // +++ base.save = function (callback, errorCallback) { @@ -446,9 +452,11 @@ function oData(res, config) { addNewResource(newResource); } + var promise = initPromise(); + //start the request with promise - if (currentPromise || typeof callback === 'undefined') { - currentPromise = Q.defer(); + if (promise && typeof callback === 'undefined') { + currentPromise = promise.defer(); startRequest(callback, errorCallback, true); return (currentPromise.promise); } @@ -460,7 +468,7 @@ function oData(res, config) { } // +++ - // adds a dataset to the current selected resource + // adds a dataset to the current selected resource // o("Product/ProductGroup").post(...) will post a dataset to the Product resource // alternative you can define a new resource by using .post({data},'OtherResource'); // +++ @@ -493,7 +501,7 @@ function oData(res, config) { if (res) { addNewResource(res); } - + if (!resource.path[resource.path.length-1] || !resource.path[resource.path.length-1].get) throwEx('Bulk updates are not supported. You need to query a unique resource with find() to patch/put it.'); @@ -523,7 +531,7 @@ function oData(res, config) { } // +++ - // Returns the current query + // Returns the current query // +++ base.query = function (overrideRes) { return (buildQuery(overrideRes)); @@ -570,7 +578,7 @@ function oData(res, config) { } return (base); } - + // +++ // Set this value to add a progress handler to the current resource // +++ @@ -583,6 +591,22 @@ function oData(res, config) { // ---------------------+++ INTERNALS +++---------------------------- + // +++ + // initialize a promise callback + // +++ + function initPromise() { + if (typeof Q !== 'undefined') { + var p = Q; + return(p); + } + else if(typeof window === 'undefined') { + var p = require('q'); + return(p); + } + else { + return(null); + } + } // +++ // builds a filter by a given data object to include or exclude values on a query @@ -604,8 +628,7 @@ function oData(res, config) { // builds a search filter // ++++ function buildSearchFilter(searchColumns, searchWord, searchFunc) { - var searchStr = ""; - var searchFunc = searchFunc || (oConfig.version == 4 ? 'contains' : 'substringof'); + searchFunc = searchFunc || (oConfig.version == 4 ? 'contains' : 'substringof'); var searchWordSplit = searchWord.split(' '); var isNotExactSearch = (searchFunc === 'contains' || searchFunc === 'substringof'); @@ -668,7 +691,7 @@ function oData(res, config) { // +++ function getQuery() { var tempStr = ''; - + for (queryName in resource.query) { if (resource.query.hasOwnProperty(queryName) && resource.query[queryName] != null) { tempStr += '&' + resource.queryList[resource.query[queryName]].name + '=' + strFormat(resource.queryList[resource.query[queryName]].value, internalParam); @@ -802,7 +825,7 @@ function oData(res, config) { if (!isQuery('$format')) { addQuery('$format', base.oConfig.format); } - + //appendings for(var i=0;i use base.queryArray + query: {}, //the query Array --> use base.queryArray queryList: [], method: 'GET', data: null, @@ -955,7 +980,7 @@ function oData(res, config) { } else { var index = uriSplit[i].split('('); - if (index.length === 1) { + if (index.length === 1 || startsWith(uriSplit[i], '(')) { reqObj.path.push({ 'resource': uriSplit[i], 'get': null }); } else { @@ -999,7 +1024,7 @@ function oData(res, config) { // +++ // internal function to check if a query exist. Otherwith throwEx a exception // queries: Could be an array or an string - // returns true if + // returns true if // +++ function isQueryThrowEx(queries) { if (isQuery(queries)) { @@ -1185,15 +1210,15 @@ function oData(res, config) { //do POST if the base.save() function was called //TODO: || res.method==='PUT' || res.method==='DELETE' else if ((res.method === 'POST' || res.method === 'PUT' || res.method === 'PATCH' || res.method === 'DELETE') && isSave) { - var stringData = stringify(res.data); + //var stringData = stringify(res.data); body += '--changeset_' + changsetGuid + '\n'; body += 'Content-Type: application/http\n'; body += 'Content-Transfer-Encoding: binary\n'; - body += 'Content-ID:' + i + 1 + '\n\n'; //This ID can be referenced $1/Customer + body += 'Content-ID:' + i + 1 + '\n\n'; //This ID can be referenced $1/Customer body += res.method + ' ' + buildQuery(res) + ' HTTP/1.1\n'; body += 'Host: ' + base.oConfig.endpoint + '\n'; body += 'Content-Type: application/json\n'; - body += 'Content-Length:' + stringData.length + '\n\n'; + //body += 'Content-Length:' + stringData.length + '\n\n'; body += stringify(resource.data) + '\n\n\n'; isChangeset = true; } @@ -1209,7 +1234,7 @@ function oData(res, config) { // start a ajax request. data should be null if nothing to send // +++ function startAjaxReq(ajaxRequest, data, callback, errorCallback, isBatch, headers, param, progress) { - + //if start loading function is set call it if (base.oConfig.start && overideLoading == null) { base.oConfig.openAjaxRequests++; @@ -1238,7 +1263,7 @@ function oData(res, config) { else if(typeof progress === 'function') { ajaxRequest.onprogress = progress; } - + ajaxRequest.onreadystatechange = function () { //check the http status if (ajaxRequest.readyState === 4) { @@ -1261,9 +1286,9 @@ function oData(res, config) { parseResponse(result[0].substring(0, result[0].length-16), tempBase); dataArray.push(tempBase.data); } - + } while (result); - + tempBase.data = dataArray; } } @@ -1318,7 +1343,7 @@ function oData(res, config) { ajaxRequest.setRequestHeader('Authorization', 'Basic ' + encodeBase64(base.oConfig.username + ':' + base.oConfig.password)); } - //check if not IE 9 or 8 + //check if not IE 9 or 8 if (!isXDomainRequest) { //set headers if (headers) { @@ -1328,7 +1353,7 @@ function oData(res, config) { } } - //additional headers + //additional headers if (base.oConfig.headers.length > 0) { //TODO: merge both normal and additional headers?! for (var i = 0; i < base.oConfig.headers.length; i++) { @@ -1398,8 +1423,17 @@ function oData(res, config) { // Create the XHR object with CORS support // +++ function createCORSRequest(method, url) { - // TODO: Add older browser fallback here! - var xhr = new XMLHttpRequest(); + var xhr = null; + + //if no window assume node.js + if(typeof window === 'undefined') { + var Xhr2 = require('xhr2'); + xhr = new Xhr2(); + } + else { + xhr = new XMLHttpRequest(); + } + if (base.oConfig.isCors && 'withCredentials' in xhr) { // XHR for Chrome/Firefox/Opera/Safari. xhr.open(method, url, base.oConfig.isAsync); @@ -1512,4 +1546,9 @@ function oData(res, config) { } return (init(res)); -} \ No newline at end of file +} + +// Export for npm +if(typeof window === 'undefined') { + module.exports = o; +} diff --git a/o.min.js b/o.min.js index d9fef29..7ed3fd9 100644 --- a/o.min.js +++ b/o.min.js @@ -1 +1 @@ -function o(e){function n(){for(var e,n={},t=0,r=arguments.length;r>t;t++)for(e in arguments[t])arguments[t].hasOwnProperty(e)&&(n[e]=arguments[t][e]);return n}return base=this,base.oConfig=base.oConfig||{endpoint:null,format:"json",version:4,strictMode:!0,start:null,ready:null,error:null,headers:[{name:"If-Match",value:"*"}],username:null,password:null,isAsync:!0,isCors:!0,openAjaxRequests:0,isHashRoute:!0,appending:""},base.config=function(e){base.oConfig=n(base.oConfig,e)},base.isEndpoint=function(){return null!==base.oConfig.endpoint},"undefined"==typeof e?base:new oData(e,base.oConfig)}function oData(e,n){function t(e,n,t,r){if(P(n)){var o="",a=[];for(i=0;i0?"?"+e.substring(1):""}function s(e){for(var n=0;n-1||e.toUpperCase().indexOf("HTTPS://")>-1?G=!1:base.oConfig.endpoint||N("You can not use resource query without defining your oData endpoint. Use o().config({endpoint:youeEndpoint}) to define your oData endpoint."),routeName=e,d(e),base)}function b(e){T("$expand")?(Y.queryList[Y.query.$expand].value+=","+e,Y.queryList[Y.query.$expand].original=Y.queryList[Y.query.$expand].value):v("$expand",e,e)}function y(e){var n=e.split("?"),t=e,r="",o={path:[],appending:"",query:{},queryList:[],method:"GET",data:null,progress:null};if(2===n.length){t=n[0],r=n[1];for(var a=r.split("&"),i=0;i0?e:void N(n+": Parameter must be set.")}function w(e,n){if("number"==typeof e)return e;var t=n;return e&&e.length>0&&(isNaN(e)||(t=parseInt(e))),t}function S(e){for(var n=0,t=0;t-1&&n++;return n}function O(e){for(var n=[],t=0;t-1&&n.push(t);for(var t=n.length-1;t>=0;t--)B.splice(n[t],1);B[0]&&(Y=B[0])}function E(e){return JSON?JSON.stringify(e):(N("No JSON support."),e)}function P(e){return"undefined"==typeof Array.isArray?"[object Array]"===e.toString():Array.isArray(e)}function A(e,n){return e?-1!==e.indexOf(n,e.length-n.length):!1}function R(e,n){return 0===e.indexOf(n)}function N(e){function n(e){this.message=e,this.name="o.js exception"}if(n.prototype=new Error,base.oConfig.strictMode===!0)throw new n(e);console.log("o.js exception: "+e)}function L(e,n){var t="",r=q(),a=!1;n&&(t+="--batch_"+e+"\n",t+="Content-Type: multipart/mixed; boundary=changeset_"+r+"\n\n");for(var i=0;i=200&&e.status<300){if(204!==e.status)if(a){var n,i=[],u=/({[\s\S]*?--batchresponse_)/g;do n=u.exec(e.responseText),n&&(H(n[0].substring(0,n[0].length-16),f),i.push(f.data));while(n);f.data=i}else H(e.responseText,f);I&&I.resolve(f),"function"==typeof t&&t.call(f,f.data,s)}else try{var p=e.responseText;if(JSON&&""!=e.responseText&&(p=JSON.parse(e.responseText)),""!==p&&p["odata.error"]){var l=p["odata.error"].message.value+" | HTTP Status: "+e.status+" | oData Code: "+p["odata.error"].code;N(l)}else N("Request to "+o()+" failed with HTTP status "+(e.status||404)+".")}catch(d){if(D(f,!0,e.status||404,e.responseText),"function"==typeof r)r(e.status||404,d);else{if(!I)throw d;d.status=e.status||404,I.reject(d)}}D(f,!1)}},base.oConfig.username&&base.oConfig.password&&(X&&N("CORS and Basic Auth is not supported for IE <= 9. Try to set isCors:false in the OData config if you do not need CORS support."),e.setRequestHeader("Authorization","Basic "+J(base.oConfig.username+":"+base.oConfig.password))),!X){if(i)for(var p=0;p0)for(var p=0;p>2,i=(3&t)<<4|r>>4,s=(15&r)<<2|o>>6,u=63&o,isNaN(r)?s=u=64:isNaN(o)&&(u=64),f=f+this._keyStr.charAt(a)+this._keyStr.charAt(i)+this._keyStr.charAt(s)+this._keyStr.charAt(u);return f},_utf8_encode:function(e){e=e.replace(/\r\n/g,"\n");for(var n="",t=0;tr?n+=String.fromCharCode(r):r>127&&2048>r?(n+=String.fromCharCode(r>>6|192),n+=String.fromCharCode(63&r|128)):(n+=String.fromCharCode(r>>12|224),n+=String.fromCharCode(r>>6&63|128),n+=String.fromCharCode(63&r|128))}return n}};return n.encode(e)}base=this;var Y=null,B=[],M=[],G=!0,I=null,U=null,X=!1,z=function(){},W={},F={"==":"eq","===":"eq","!=":"ne","!==":"ne",">":"gt",">=":"ge","<":"lt","<=":"le","&&":"and","||":"or","!":"not","+":"add","-":"sub","*":"mul",".":"/","%":"mod"};return base.data=[],base.inlinecount=null,base.param={},base.oConfig=n,base.routes=base.route=function(e,n){P(e)||(e=[e]);for(var t=window.location.hash,r=0;rt;t++)for(e in arguments[t])arguments[t].hasOwnProperty(e)&&(n[e]=arguments[t][e]);return n}var t=this;return t.oConfig=t.oConfig||{endpoint:null,format:"json",version:4,strictMode:!0,start:null,ready:null,error:null,headers:[{name:"If-Match",value:"*"}],username:null,password:null,isAsync:!0,isCors:!0,openAjaxRequests:0,isHashRoute:!0,appending:""},t.config=function(e){t.oConfig=n(t.oConfig,e)},t.isEndpoint=function(){return null!==t.oConfig.endpoint},"undefined"==typeof e?t:new oData(e,t.oConfig)}function oData(e,n){function t(){if("undefined"!=typeof Q){var e=Q;return e}if("undefined"==typeof window){var e=require("q");return e}return null}function r(e,n,t,r){if(R(n)){var o="",a=[];for(i=0;i0?"?"+e.substring(1):""}function s(e){for(var n=0;n-1||e.toUpperCase().indexOf("HTTPS://")>-1?U=!1:B.oConfig.endpoint||k("You can not use resource query without defining your oData endpoint. Use o().config({endpoint:youeEndpoint}) to define your oData endpoint."),routeName=e,h(e),B)}function v(e){w("$expand")?(M.queryList[M.query.$expand].value+=","+e,M.queryList[M.query.$expand].original=M.queryList[M.query.$expand].value):C("$expand",e,e)}function m(e){var n=e.split("?"),t=e,r="",o={path:[],appending:"",query:{},queryList:[],method:"GET",data:null,progress:null};if(2===n.length){t=n[0],r=n[1];for(var a=r.split("&"),i=0;i0?e:void k(n+": Parameter must be set.")}function O(e,n){if("number"==typeof e)return e;var t=n;return e&&e.length>0&&(isNaN(e)||(t=parseInt(e))),t}function E(e){for(var n=0,t=0;t-1&&n++;return n}function P(e){for(var n=[],t=0;t-1&&n.push(t);for(var t=n.length-1;t>=0;t--)G.splice(n[t],1);G[0]&&(M=G[0])}function b(e){return JSON?JSON.stringify(e):(k("No JSON support."),e)}function R(e){return"undefined"==typeof Array.isArray?"[object Array]"===e.toString():Array.isArray(e)}function A(e,n){return e?-1!==e.indexOf(n,e.length-n.length):!1}function N(e,n){return 0===e.indexOf(n)}function k(e){function n(e){this.message=e,this.name="o.js exception"}if(n.prototype=new Error,B.oConfig.strictMode===!0)throw new n(e);console.log("o.js exception: "+e)}function L(e,n){var t="",r=$(),o=!1;n&&(t+="--batch_"+e+"\n",t+="Content-Type: multipart/mixed; boundary=changeset_"+r+"\n\n");for(var i=0;i=200&&e.status<300){if(204!==e.status)if(o){var n,i=[],s=/({[\s\S]*?--batchresponse_)/g;do n=s.exec(e.responseText),n&&(j(n[0].substring(0,n[0].length-16),f),i.push(f.data));while(n);f.data=i}else j(e.responseText,f);X&&X.resolve(f),"function"==typeof t&&t.call(f,f.data,u)}else try{var p=e.responseText;if(JSON&&""!=e.responseText&&(p=JSON.parse(e.responseText)),""!==p&&p["odata.error"]){var l=p["odata.error"].message.value+" | HTTP Status: "+e.status+" | oData Code: "+p["odata.error"].code;k(l)}else k("Request to "+a()+" failed with HTTP status "+(e.status||404)+".")}catch(d){if(H(f,!0,e.status||404,e.responseText),"function"==typeof r)r(e.status||404,d);else{if(!X)throw d;d.status=e.status||404,X.reject(d)}}H(f,!1)}},B.oConfig.username&&B.oConfig.password&&(W&&k("CORS and Basic Auth is not supported for IE <= 9. Try to set isCors:false in the OData config if you do not need CORS support."),e.setRequestHeader("Authorization","Basic "+Y(B.oConfig.username+":"+B.oConfig.password))),!W){if(i)for(var p=0;p0)for(var p=0;p>2,i=(3&t)<<4|r>>4,u=(15&r)<<2|o>>6,s=63&o,isNaN(r)?u=s=64:isNaN(o)&&(s=64),f=f+this._keyStr.charAt(a)+this._keyStr.charAt(i)+this._keyStr.charAt(u)+this._keyStr.charAt(s);return f},_utf8_encode:function(e){e=e.replace(/\r\n/g,"\n");for(var n="",t=0;tr?n+=String.fromCharCode(r):r>127&&2048>r?(n+=String.fromCharCode(r>>6|192),n+=String.fromCharCode(63&r|128)):(n+=String.fromCharCode(r>>12|224),n+=String.fromCharCode(r>>6&63|128),n+=String.fromCharCode(63&r|128))}return n}};return n.encode(e)}var B=this,M=null,G=[],I=[],U=!0,X=null,z=null,W=!1,F=function(){},K={},V={"==":"eq","===":"eq","!=":"ne","!==":"ne",">":"gt",">=":"ge","<":"lt","<=":"le","&&":"and","||":"or","!":"not","+":"add","-":"sub","*":"mul",".":"/","%":"mod"};return B.data=[],B.inlinecount=null,B.param={},B.oConfig=n,B.routes=B.route=function(e,n){R(e)||(e=[e]),"undefined"==typeof window&&k("Routes are only supported in a browser env.");for(var t=window.location.hash,r=0;r o.js - Test page - + - - + +
- - + + - - + + diff --git a/test/q.js b/spec/browser/q.js similarity index 100% rename from test/q.js rename to spec/browser/q.js diff --git a/test/test.js b/spec/browser/test.js similarity index 73% rename from test/test.js rename to spec/browser/test.js index 1f0754c..3a759a2 100644 --- a/test/test.js +++ b/spec/browser/test.js @@ -4,27 +4,31 @@ // o.js unit test with quint.js // http://qunitjs.com/ // -// By Jan Hommes -// Date: 15.06.2015 +// By Jan Hommes +// Date: 01.02.2016 // +++ +/*if(typeof require === 'undefined') { + var o = require('../o.js'); +}*/ + //helper function for debuging function printResult(o,data) { try{ if(data.message) { - return('JSON Result: '+JSON.stringify(data)); + return('JSON Result: '+JSON.stringify(data)); } return('Lenght: '+(typeof data.length !== 'undefined'? data.length :1) +' | JSON Result: '+JSON.stringify(data)); } catch(e) { return('Exception: '+e); } -} +} //helper function to configure endpoint function configureEndpoint() { if(!o().isEndpoint()) { o().config({ - endpoint:'http://services.odata.org/V4/%28S%28wptr35qf3bz4kb5oatn432ul%29%29/TripPinServiceRW/', + endpoint: 'http://services.odata.org/V4/(S(ms4wufavzmwsg3fjo3eqdgak))/TripPinServiceRW/', version:4, strictMode:true }); @@ -34,7 +38,7 @@ function configureEndpoint() { // ----------------------------------------------- Tests ---------------------------------------------------- -QUnit.test('No resource or endpoint throw error', function(assert) { +test('No resource or endpoint throw error', function(assert) { assert.throws( function() { o(''); @@ -49,6 +53,19 @@ QUnit.test('No resource or endpoint throw error', function(assert) { ); }); + +QUnit.test('GET People - no endpoint - no query', function(assert) { + var done = assert.async(); + + o('http://services.odata.org/V4/(S(ms4wufavzmwsg3fjo3eqdgak))/TripPinServiceRW/People').get(function(data) { + assert.ok(data.length >= 0, printResult(this,data)); + done(); + }, function(e) { + assert.ok(e === 200, printResult(this, e)); + done(); + }); +}); + QUnit.test('CONFIG - endpoint', function(assert) { configureEndpoint(); assert.ok(o().isEndpoint(), 'Passed! Endpoint is: '+o('').query()); @@ -68,21 +85,33 @@ function createTestData() { done(); testEntity = data; startEndpointTests(); - }, function(e) { + }, function(e) { assert.ok(e === 200, printResult(this, e)); done() }); }); } - + // Start this test if the endpoint is configured function startEndpointTests() { - QUnit.test('GET People - endpoint - no query', function(assert) { + QUnit.test('POST People as the test person - endpoint - no query', function(assert) { + var done = assert.async(); + var name='Test_'+Math.random(); + o('People').post({UserName:name,FirstName:name,LastName:name}).save(function(data) { + assert.ok(data.UserName === name, printResult(this, data)); + done(); + }, function(e) { + assert.ok(e === 200, printResult(this, e)); + done() + }); + }); + + QUnit.test('GET People - no endpoint - no query', function(assert) { var done = assert.async(); - o('People').get(function(data) { + o('http://services.odata.org/V4/(S(ms4wufavzmwsg3fjo3eqdgak))/TripPinServiceRW/People').get(function(data) { assert.ok(data.length >= 0, printResult(this,data)); done(); - }, function(e) { + }, function(e) { assert.ok(e === 200, printResult(this, e)); done() }); @@ -93,7 +122,7 @@ function startEndpointTests() { o('People').top(1).get(function(data) { assert.ok(data.length >= 0, printResult(this,data)); done(); - }, function(e) { + }, function(e) { assert.ok(e === 200, printResult(this, e)); done() }); @@ -104,7 +133,7 @@ function startEndpointTests() { o('People(\''+testEntity.UserName+'\')').get(function(data) { assert.ok(data.UserName === testEntity.UserName, printResult(this,data)); done(); - }, function(e) { + }, function(e) { assert.ok(e === 200, printResult(this, e)); done() }); @@ -115,7 +144,7 @@ function startEndpointTests() { o('People').take(5).skip(2).get(function(data) { assert.ok(data.length >= 0, printResult(this,data)); done(); - }, function(e) { + }, function(e) { assert.ok(e === 200, printResult(this, e)); done() }); @@ -126,7 +155,7 @@ function startEndpointTests() { o('People').take(5).expand('Trips').get(function(data) { assert.ok(data.length >= 0 && (data[0] && data[0].Trips), printResult(this,data)); done(); - }, function(e) { + }, function(e) { assert.ok(e === 200, printResult(this, e)); done() }); @@ -137,7 +166,7 @@ function startEndpointTests() { o('People(\''+testEntity.UserName+'\')').get().then(function(o) { assert.ok(o.data.UserName === testEntity.UserName, printResult(o,o.data)); done(); - }, function(e) { + }, function(e) { assert.ok(e === 200, printResult(this, e)); done() }); @@ -160,9 +189,9 @@ function startEndpointTests() { QUnit.test('GET People(\''+testEntity.UserName+'\') and PATCH AAA People(\''+testEntity.UserName+'\'), change and save() it with q.js promise - endpoint - no query', function(assert) { var done1 = assert.async(); - var done2 = assert.async(); + var done2 = assert.async(); var name='Test_'+Math.random(); - + o('People(\''+testEntity.UserName+'\')').get().then(function(o) { o.data.FirstName=name; assert.ok(o.data.UserName===testEntity.UserName, printResult(o,o.data)); @@ -177,12 +206,12 @@ function startEndpointTests() { done2(); }); }); - + /*QUnit.test('GET People(\''+testEntity.UserName+'\') and PATCH People(X), change and save() it with q.js promise but provoke error - endpoint - no query', function(assert) { var done1 = assert.async(); - var done2 = assert.async(); + var done2 = assert.async(); var name='Test_'+Math.random(); - + o('People(\''+testEntity.UserName+'\')').get().then(function(o) { assert.ok(o.data.UserName===testEntity.UserName, printResult(o,o.data)); o.data.Gender = 1; @@ -202,20 +231,20 @@ function startEndpointTests() { o('People(\''+testEntity.UserName+'\')').patch({FirstName:name}).save(function(data) { assert.ok(data.length===0, printResult(this,data)); done(); - }, function(e) { + }, function(e) { assert.ok(e === 200, printResult(this, e)); done(); }); }); - - + + QUnit.test('PATCH People(\''+testEntity.UserName+'\') - no endpoint - no query', function(assert) { var done = assert.async(); var name='Test_'+Math.random(); - o('http://services.odata.org/V4/%28S%28wptr35qf3bz4kb5oatn432ul%29%29/TripPinServiceRW/People(\''+testEntity.UserName+'\')').patch({FirstName:name}).save(function(data) { + o('http://services.odata.org/V4/(S(ms4wufavzmwsg3fjo3eqdgak))/TripPinServiceRW/People(\''+testEntity.UserName+'\')').patch({FirstName:name}).save(function(data) { assert.ok(data.length===0, printResult(this,data)); done(); - }, function(e) { + }, function(e) { assert.ok(e === 200, printResult(this, e)); done(); }); @@ -227,10 +256,31 @@ function startEndpointTests() { var name='Test_'+Math.random(); o('People(\''+testEntity.UserName+'\')').delete().save(function(data) { assert.ok(data.length===0, printResult(this,data)); - done(); - }, function(e) { + done(); + }, function(e) { assert.ok(e === 200, printResult(this, e)); done(); }); }); + + + QUnit.test('Route #test', function(assert) { + var done = assert.async(); + o('People').route('test', function(data) { + assert.ok(data.length > 0); + done(); + }); + window.location.hash = "test"; + }); + + QUnit.test('Route #test/:0 -> where :0 is ' + testEntity.UserName, function(assert) { + var done = assert.async(); + o('People').filter('UserName == \':0\'').route('person/:0', function(data) { + assert.ok(data.length > 0); + //assert.ok(this.param[0] === testEntity.UserName); + assert.ok(this.param[0] === 'russellwhyte'); + done(); + }); + window.location.hash = "person/russellwhyte"; + }); } diff --git a/spec/o.spec.js b/spec/o.spec.js new file mode 100644 index 0000000..1b87643 --- /dev/null +++ b/spec/o.spec.js @@ -0,0 +1,208 @@ +var o = require('../o.js'); + +describe('o.js tests:', function() { + + it('GET People - no endpoint - no query', function(done) { + o('http://services.odata.org/V4/(S(ms4wufavzmwsg3fjo3eqdgak))/TripPinServiceRW/People').get(function(data) { + expect(data.length).not.toBe(0); + done(); + + }, function(e) { + expect(e).toBe(200); + done(); + }); + }); + + it('GET People - no endpoint - no query', function(done) { + o('http://services.odata.org/V4/(S(ms4wufavzmwsg3fjo3eqdgak))/TripPinServiceRW/People').get(function(data) { + expect(data.length).not.toBe(0); + done(); + }, function(e) { + expect(e).toBe(200); + done() + }); + }); + + var testEntity = null; + + describe('with endpoint:', function() { + + beforeAll(function(done) { + o().config({ + endpoint: 'http://services.odata.org/V4/(S(ms4wufavzmwsg3fjo3eqdgak))/TripPinServiceRW/', + version:4, + strictMode:true + }); + + var name='Test_'+Math.random(); + o('People').post({UserName:name,FirstName:name,LastName:name}).save(function(data) { + testEntity = data; + done(); + }, function(e) { + expect(e).toBe(200); + }); + }); + + it('POST People as the test person - endpoint - no query', function(done) { + var name='Test_'+Math.random(); + o('People').post({UserName:name,FirstName:name,LastName:name}).save(function(data) { + expect(data.UserName).toBe(name); + done(); + }, function(e) { + expect(e).toBe(200); + done(); + }); + }); + + it('GET People - endpoint - .top(1)', function(done) { + o('People').top(1).get(function(data) { + expect(data.length).not.toBe(0); + done(); + }, function(e) { + expect(e).toBe(200); + done(); + }); + }); + + it('GET People(testEntity) - endpoint - no query', function(done) { + o('People(\''+testEntity.UserName+'\')').get(function(data) { + expect(data.UserName).toBe(testEntity.UserName); + done(); + }, function(e) { + expect(e).toBe(200); + done(); + }); + }); + + it('GET People - endpoint - query: .take(5) and .skip(2)', function(done) { + o('People').take(5).skip(2).get(function(data) { + expect(data.length).not.toBe(0); + done(); + }, function(e) { + expect(e).toBe(200); + done(); + }); + }); + + it('GET People - endpoint - query: .take(5) and .expand("Trips")', function(done) { + o('People').take(5).expand('Trips').get(function(data) { + expect(data.length).not.toBe(0); + expect(typeof data[0]).not.toBe('undefined'); + expect(typeof data[0].Trips).not.toBe('undefined'); + done(); + }, function(e) { + expect(e).toBe(200); + done() + }); + }); + + it('GET People(testEntity) with q.js promise - endpoint - no query', function(done) { + o('People(\''+testEntity.UserName+'\')').get().then(function(o) { + expect(o.data.UserName).toBe(testEntity.UserName); + done(); + }, function(e) { + expect(e).toBe(200); + done() + }); + }); + + it('GET People(testEntity) and Group with q.js promise all - endpoint - no query', function(done) { + var Q = require('q'); + Q.all([ + o('People(\''+testEntity.UserName+'\')').get(), + o('People?$filter=UserName eq \'Yeah\'') + ]).then(function(o) { + expect(o[0].data.UserName).toBe(testEntity.UserName); + expect(o[1].data.length).toBe(0); + done(); + }).fail(function(err) { + expect(true).toBe(false); + done(); + }); + }); + + it('GET People(testEntity) and PATCH People(testEntity), change and save() it with q.js promise - endpoint - no query', function(done) { + var name='Test_'+Math.random(); + + o('People(\''+testEntity.UserName+'\')').get().then(function(o) { + o.data.FirstName=name; + expect(o.data.UserName).toBe(testEntity.UserName); + return(o.save()); + }).then(function(o) { + expect(o.data.UserName).toBe(testEntity.UserName); + expect(o.data.FirstName).toBe(name); + done(); + }).fail(function(err) { + expect(true).toBe(false); + done(); + }); + }); + + /*done('GET People(\''+testEntity.UserName+'\') and PATCH People(X), change and save() it with q.js promise but provoke error - endpoint - no query', function(assert) { + var done1 = assert.async(); + var done2 = assert.async(); + var name='Test_'+Math.random(); + + o('People(\''+testEntity.UserName+'\')').get().then(function(o) { + expect(o.data.UserName===testEntity.UserName, printResult(o,o.data)); + o.data.Gender = 1; + done1(); + return(o.save()); + }).then(function(o) { + //not reachable because of error + }).fail(function(err) { + expect(err, 'Passed! Error as expected.'); + done2(); + }); + });*/ + + it('PATCH People(testEntity) - endpoint - no query', function(done) { + var name='Test_'+Math.random(); + o('People(\''+testEntity.UserName+'\')').patch({FirstName:name}).save(function(data) { + expect(data.length).toBe(0); + done(); + }, function(e) { + expect(e).toBe(200); + done(); + }); + }); + + + it('PATCH People(testEntity) - no endpoint - no query', function(done) { + var name='Test_'+Math.random(); + o('http://services.odata.org/V4/(S(ms4wufavzmwsg3fjo3eqdgak))/TripPinServiceRW/People(\''+testEntity.UserName+'\')').patch({FirstName:name}).save(function(data) { + expect(data.length).toBe(0); + done(); + }, function(e) { + expect(e).toBe(200); + done(); + }); + }); + + //DELETES the test data, move it to the end of this file! + it('DELETE Products(testEntity) - endpoint - no query', function(done) { + var name='Test_'+Math.random(); + o('People(\''+testEntity.UserName+'\')').delete().save(function(data) { + expect(data.length).toBe(0); + done(); + }, function(e) { + expect(e).toBe(200); + done(); + }); + }); + + it('Route -> error', function(done) { + var name='Test_'+Math.random(); + try { + o('People').route('test', function(data) { + }); + } + catch(ex) { + done(); + expect(true).toBe(true); + } + + }); + }); + +}); \ No newline at end of file diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json new file mode 100644 index 0000000..3ea3166 --- /dev/null +++ b/spec/support/jasmine.json @@ -0,0 +1,11 @@ +{ + "spec_dir": "spec", + "spec_files": [ + "**/*[sS]pec.js" + ], + "helpers": [ + "helpers/**/*.js" + ], + "stopSpecOnExpectationFailure": false, + "random": false +}