By completing this tutorial you will be able to consume an OData service from JavaScript with the elegance and simplicity of the JavaScript Language Query, without ever writing down an $.ajax statement or manually building OData query strings or payloads.
Notes:
- This howto is built with the Northwind sample database, wrapped in an EntityFramework 4.0 EDMX and published with a WCF Data Service. You can grab this example project from CodePlex.
- This walkthrough intentionally lacks the use of any kind of view or template engines to keep the material focused on its main topic: accessing and manipulating data with JayData.
Why use JayData?
With JayData you can work with online data in JavaScript on a way that was previously only possible for .NET and Silverlight developers. Instead of approaching the OData (or other storage) endpoint from a protocol level with handcrafted query strings and HTTP requests fired off from the middle of an event handler, JayData provides a typed abstraction layer: the EntityContext with typed EntitySets and Entities, and the native syntax of JavaScript for querying and modifying data.
Before we begin, see it in action below:
One possible way |
The JayData way
|
Get ALL Categories!
|
|
Get ONLY the product names from the list of products that are either Beverages (Category_Id = 1) or contain tofu in the product name regardless of the case,
sorted by the product name.
|
|
A typed OData context provides intellisense on the Entity and EntitySet level.
You can generate such a context with the JaySvcUtil.exe, we will talk this about later.
CategoryId? CategoryID? or Category_ID?
Intellisense list of entity fields comes in handy in a number of times.
The fluent API provides for simple, readable code
…that is easy to maintain even in the long run
How to setup the Northwind OData demo environemnt?
The Northwind OData demo environment can be downloaded from here or from this link at CodePlex.
1) Open Visual Studio 2010 and create a new Empty Web Application, name it NorthwindDemo.
2) Add ASP.NET folder App_Data
3) Drag the Northwind.sdf sample database from
C:\Program Files (x86)\Microsoft SQL Server Compact Edition\v4.0\Samples into the App_Data folder.
4) Add a new ADO.NET Entity Data Model and name it Northwind
5) Choose Generate from database
6) Accept the default selection of the data connection
If you can not see Northwind.sdf like in the picture then make sure you have SQL Server CE 4.0 support installed. Also you might need to open the Northwind.sdf database with a double click to have it appear in the list.
7) Include all the tables and press Finish to get an EDMX (that is not so nicely arranged then mine)
8) Add a new WCF Data Service and name it Northwind.svc
9) Open the Northwind.svc file and edit its contents following the comments in the code:
1 2 3 4 5 6 7 8 9 10 11 |
namespace NorthwindDemo { public class Northwind : DataService<NorthwindEntities> { public static void InitializeService(DataServiceConfiguration config) { config.SetEntitySetAccessRule("*", EntitySetRights.All); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; } } } |
10) Right click on the Northwind.svc and select “View in Browser”. Congratulation, you have and operational OData service.
Getting JayData and preparing our HTML5 application
1) Open Tools > Library Package Manager > Package Manager Console, and type
2) Install-Package JayData
3) The package contains the JayData JavaScript library (and its dependencies) and the JaySvcUtil.exe command line tool.
4) Use the JaySvcUtil to generate Northwind.js from the service metadata uri: /Northwind.svc/$metadata
1 |
C:\JayDataDemo\NorthwindDemo\JaySvcUtil -m http://yoursite:yourport/Northwind.svc/$metadata -o Northwind.js |
The syntax for JaysvcUtil is blogged about here.
Northwind.js is generated in you web project folder. You may have to switch “show all files” in the solution tree to be able to access Northwind.js from Visual Studio.
For those with deep passion in their heart toward JavaScript, here is what gets generated. Others may freely skip.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
////////////////////////////////////////////////////////////////////////////////////// ////// Autogenerated by JaySvcUtil 1.0.2, copyright http://JayData.org/licensing //////////////////////////////////////////////////////////////////////////////////////</pre> (function(global, $data, undefined) { function registerEdmTypes() { function Edm_Boolean() { }; $data.Container.registerType('Edm.Boolean', Edm_Boolean); $data.Container.mapType(Edm_Boolean, $data.Boolean); function Edm_Binary() { }; $data.Container.registerType('Edm.Binary', Edm_Binary); $data.Container.mapType(Edm_Binary, $data.Blob); function Edm_DateTime() { }; $data.Container.registerType('Edm.DateTime', Edm_DateTime); $data.Container.mapType(Edm_DateTime, $data.Date); function Edm_DateTimeOffset() { }; $data.Container.registerType('Edm.DateTimeOffset', Edm_DateTimeOffset); $data.Container.mapType(Edm_DateTimeOffset, $data.Integer); function Edm_Time() { }; $data.Container.registerType('Edm.Time', Edm_Time); $data.Container.mapType(Edm_Time, $data.Integer); function Edm_Decimal() { }; $data.Container.registerType('Edm.Decimal', Edm_Decimal); $data.Container.mapType(Edm_Decimal, $data.Number); function Edm_Single() { }; $data.Container.registerType('Edm.Single', Edm_Single); $data.Container.mapType(Edm_Single, $data.Number); function Edm_Double() { }; $data.Container.registerType('Edm.Double', Edm_Double); $data.Container.mapType(Edm_Double, $data.Number); function Edm_Guid() { }; $data.Container.registerType('Edm.Guid', Edm_Guid); $data.Container.mapType(Edm_Guid, $data.String); function Edm_Int16() { }; $data.Container.registerType('Edm.Int16', Edm_Int16); $data.Container.mapType(Edm_Int16, $data.Integer); function Edm_Int32() { }; $data.Container.registerType('Edm.Int32', Edm_Int32); $data.Container.mapType(Edm_Int32, $data.Integer); function Edm_Int64() { }; $data.Container.registerType('Edm.Int64', Edm_Int64); $data.Container.mapType(Edm_Int64, $data.Integer); function Edm_Byte() { }; $data.Container.registerType('Edm.Byte', Edm_Byte); $data.Container.mapType(Edm_Byte, $data.Integer); function Edm_String() { }; $data.Container.registerType('Edm.String', Edm_String); $data.Container.mapType(Edm_String, $data.String); }; registerEdmTypes(); $data.Entity.extend('NorthwindModel.Category', { 'Category_ID': { key:true,type:'Edm.Int32',nullable:false,computed:true }, 'Category_Name': { type:'Edm.String',nullable:false,required:true,maxLength:15 }, 'Description': { type:'Edm.String',nullable:true,maxLength:Number.POSITIVE_INFINITY }, 'Picture': { type:'Edm.Binary',nullable:true,maxLength:Number.POSITIVE_INFINITY }, 'Products': { type:'Array',elementType:'NorthwindModel.Product',inverseProperty:'Category' } }); $data.Entity.extend('NorthwindModel.Customer', { 'Customer_ID': { key:true,type:'Edm.String',nullable:false,required:true,maxLength:5 }, 'Company_Name': { type:'Edm.String',nullable:false,required:true,maxLength:40 }, 'Contact_Name': { type:'Edm.String',nullable:true,maxLength:30 }, 'Contact_Title': { type:'Edm.String',nullable:true,maxLength:30 }, 'Address': { type:'Edm.String',nullable:true,maxLength:60 }, 'City': { type:'Edm.String',nullable:true,maxLength:15 }, 'Region': { type:'Edm.String',nullable:true,maxLength:15 }, 'Postal_Code': { type:'Edm.String',nullable:true,maxLength:10 }, 'Country': { type:'Edm.String',nullable:true,maxLength:15 }, 'Phone': { type:'Edm.String',nullable:true,maxLength:24 }, 'Fax': { type:'Edm.String',nullable:true,maxLength:24 }, 'Orders': { type:'Array',elementType:'NorthwindModel.Order',inverseProperty:'Customer' } }); $data.Entity.extend('NorthwindModel.Employee', { 'Employee_ID': { key:true,type:'Edm.Int32',nullable:false,computed:true }, 'Last_Name': { type:'Edm.String',nullable:false,required:true,maxLength:20 }, 'First_Name': { type:'Edm.String',nullable:false,required:true,maxLength:10 }, 'Title': { type:'Edm.String',nullable:true,maxLength:30 }, 'Birth_Date': { type:'Edm.DateTime',nullable:true }, 'Hire_Date': { type:'Edm.DateTime',nullable:true }, 'Address': { type:'Edm.String',nullable:true,maxLength:60 }, 'City': { type:'Edm.String',nullable:true,maxLength:15 }, 'Region': { type:'Edm.String',nullable:true,maxLength:15 }, 'Postal_Code': { type:'Edm.String',nullable:true,maxLength:10 }, 'Country': { type:'Edm.String',nullable:true,maxLength:15 }, 'Home_Phone': { type:'Edm.String',nullable:true,maxLength:24 }, 'Extension': { type:'Edm.String',nullable:true,maxLength:4 }, 'Photo': { type:'Edm.Binary',nullable:true,maxLength:Number.POSITIVE_INFINITY }, 'Notes': { type:'Edm.String',nullable:true,maxLength:Number.POSITIVE_INFINITY }, 'Reports_To': { type:'Edm.Int32',nullable:true }, 'Orders': { type:'Array',elementType:'NorthwindModel.Order',inverseProperty:'Employee' } }); $data.Entity.extend('NorthwindModel.Order_Detail', { 'Order_ID': { key:true,type:'Edm.Int32',nullable:false,required:true }, 'Product_ID': { key:true,type:'Edm.Int32',nullable:false,required:true }, 'Unit_Price': { type:'Edm.Decimal',nullable:false,required:true }, 'Quantity': { type:'Edm.Int16',nullable:false,required:true }, 'Discount': { type:'Edm.Single',nullable:false,required:true }, 'Product': { type:'NorthwindModel.Product',required:true,inverseProperty:'Order_Details' }, 'Order': { type:'NorthwindModel.Order',required:true,inverseProperty:'Order_Details' } }); $data.Entity.extend('NorthwindModel.Order', { 'Order_ID': { key:true,type:'Edm.Int32',nullable:false,required:true }, 'Customer_ID': { type:'Edm.String',nullable:false,required:true,maxLength:5 }, 'Employee_ID': { type:'Edm.Int32',nullable:true }, 'Ship_Name': { type:'Edm.String',nullable:true,maxLength:40 }, 'Ship_Address': { type:'Edm.String',nullable:true,maxLength:60 }, 'Ship_City': { type:'Edm.String',nullable:true,maxLength:15 }, 'Ship_Region': { type:'Edm.String',nullable:true,maxLength:15 }, 'Ship_Postal_Code': { type:'Edm.String',nullable:true,maxLength:10 }, 'Ship_Country': { type:'Edm.String',nullable:true,maxLength:15 }, 'Ship_Via': { type:'Edm.Int32',nullable:true }, 'Order_Date': { type:'Edm.DateTime',nullable:true }, 'Required_Date': { type:'Edm.DateTime',nullable:true }, 'Shipped_Date': { type:'Edm.DateTime',nullable:true }, 'Freight': { type:'Edm.Decimal',nullable:true }, 'Customer': { type:'NorthwindModel.Customer',required:true,inverseProperty:'Orders' }, 'Employee': { type:'NorthwindModel.Employee',inverseProperty:'Orders' }, 'Order_Details': { type:'Array',elementType:'NorthwindModel.Order_Detail',inverseProperty:'Order' }, 'Shipper': { type:'NorthwindModel.Shipper',inverseProperty:'Orders' } }); $data.Entity.extend('NorthwindModel.Product', { 'Product_ID': { key:true,type:'Edm.Int32',nullable:false,computed:true }, 'Supplier_ID': { type:'Edm.Int32',nullable:true }, 'Category_ID': { type:'Edm.Int32',nullable:true }, 'Product_Name': { type:'Edm.String',nullable:false,required:true,maxLength:40 }, 'English_Name': { type:'Edm.String',nullable:true,maxLength:40 }, 'Quantity_Per_Unit': { type:'Edm.String',nullable:true,maxLength:20 }, 'Unit_Price': { type:'Edm.Decimal',nullable:true }, 'Units_In_Stock': { type:'Edm.Int16',nullable:true }, 'Units_On_Order': { type:'Edm.Int16',nullable:true }, 'Reorder_Level': { type:'Edm.Int16',nullable:true }, 'Discontinued': { type:'Edm.Boolean',nullable:false,required:true }, 'Category': { type:'NorthwindModel.Category',inverseProperty:'Products' }, 'Order_Details': { type:'Array',elementType:'NorthwindModel.Order_Detail',inverseProperty:'Product' }, 'Supplier': { type:'NorthwindModel.Supplier',inverseProperty:'Products' } }); $data.Entity.extend('NorthwindModel.Shipper', { 'Shipper_ID': { key:true,type:'Edm.Int32',nullable:false,computed:true }, 'Company_Name': { type:'Edm.String',nullable:false,required:true,maxLength:40 }, 'Orders': { type:'Array',elementType:'NorthwindModel.Order',inverseProperty:'Shipper' } }); $data.Entity.extend('NorthwindModel.Supplier', { 'Supplier_ID': { key:true,type:'Edm.Int32',nullable:false,computed:true }, 'Company_Name': { type:'Edm.String',nullable:false,required:true,maxLength:40 }, 'Contact_Name': { type:'Edm.String',nullable:true,maxLength:30 }, 'Contact_Title': { type:'Edm.String',nullable:true,maxLength:30 }, 'Address': { type:'Edm.String',nullable:true,maxLength:60 }, 'City': { type:'Edm.String',nullable:true,maxLength:15 }, 'Region': { type:'Edm.String',nullable:true,maxLength:15 }, 'Postal_Code': { type:'Edm.String',nullable:true,maxLength:10 }, 'Country': { type:'Edm.String',nullable:true,maxLength:15 }, 'Phone': { type:'Edm.String',nullable:true,maxLength:24 }, 'Fax': { type:'Edm.String',nullable:true,maxLength:24 }, 'Products': { type:'Array',elementType:'NorthwindModel.Product',inverseProperty:'Supplier' } }); $data.EntityContext.extend('NorthwindDemo.NorthwindEntities', { Categories: { type: $data.EntitySet, elementType: NorthwindModel.Category }, Customers: { type: $data.EntitySet, elementType: NorthwindModel.Customer }, Employees: { type: $data.EntitySet, elementType: NorthwindModel.Employee }, Order_Details: { type: $data.EntitySet, elementType: NorthwindModel.Order_Detail }, Orders: { type: $data.EntitySet, elementType: NorthwindModel.Order }, Products: { type: $data.EntitySet, elementType: NorthwindModel.Product }, Shippers: { type: $data.EntitySet, elementType: NorthwindModel.Shipper }, Suppliers: { type: $data.EntitySet, elementType: NorthwindModel.Supplier } }); })(window, $data); |
5) Include Northwind.js in your project and place into the Scripts folder
6) Add an empty html page and name it index.html
Make sure that the doctype is HTML5 that is: <!DOCTYPE html>
7) Place JayData.js and its dependencies in the index.html file
UPDATE:
As of JayData 1.1 and above you need to also include jaydataproviders/odataprovider.js in your page for the following examples to work without modification.
How to create a simple product creator application?
List categories
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <script src="Scripts/datajs-1.0.3.js" type="text/javascript"></script> <script src="Scripts/jquery-1.6.4.js" type="text/javascript"></script> <script src="Scripts/JayData-rc1-r1.js" type="text/javascript"></script> <script src="Scripts/Northwind.js" type="text/javascript"></script> <script> //copy this line from Northwind.js northwind = new NorthwindDemo.NorthwindEntities({ name: 'oData', oDataServiceHost: '/Northwind.svc' }); $(document).ready(function () { northwind.Categories.forEach(function (category) { var item = "<li class=@cls data-id=@id><a href=#>@name</a></li>" .replace("@cls", 'category') .replace("@id", category.Category_ID) .replace("@name", category.Category_Name); $('#categories').append(item); }); }); </script> <title></title> </head> <body> <h1>Categories</h1> <ul id="categories"></ul> <h1>Products</h1> <ul id="products"></ul> </body> </html> |
List products for a category
Extended the script block with the following lines. Notice the match in the jQuery delegate for the category class (class=category).
1 2 3 4 5 6 7 8 9 10 11 12 13 |
... var selectedCategoryID; $(document).delegate('.category', 'click', function () { selectedCategoryID = $(this).data('id'); northwind.Products .filter(function (product) { return product.Category_ID == selectedCategoryID }) .forEach(function (product) { $('#products').append($('<li>').text(product.Product_Name)) }); }); </script> |
Create a new product from code
1 2 3 4 5 6 7 8 9 10 11 12 |
function addNewProduct() { var product = new NorthwindModel.Product(); product.Product_Name = "Tea for two #" + Math.random().toString().substr(1,3); product.English_Name = "JayData tea"; //product.Category = new NorthwindModel.Category({ Category_ID: 1 }); product.Category_ID = 1; product.Supplier_ID = 1; northwind.add(product); northwind.saveChanges(function () { alert('New produt created with ID: ' + product.Product_ID); }); } |
To invoke this function place a button somewhere in the html.
1 |
<button onclick="addNewProduct()">Press to create a product</button> |
and that’s all folks…