Skip to content

Commit

Permalink
Merge pull request shackbarth#7 from shackbarth/icecream
Browse files Browse the repository at this point in the history
part 2 of the tutorial with the ice cream sample code written
  • Loading branch information
John Rogelstad committed Jul 11, 2013
2 parents 9809c75 + 7e31c9e commit c437b6a
Show file tree
Hide file tree
Showing 15 changed files with 272 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/TUTORIAL.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## xTuple Extension Tutorial
### Part I: Adding a new business object

## Overview

Expand Down
144 changes: 144 additions & 0 deletions docs/TUTORIAL2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
## xTuple Extension Tutorial
### Part II: Extending a business object

Having completed **Part I** of our tutorial, we can now manage `IceCreamFlavors` per the customer's requirements. Now we need to be able to extend the `Contact` business object to add `IceCreamFlavor` as a field.

### Tables

In a perfect world, we would just go into the `cntct` table and add a column. This is not an option. We're writing a humble extension here! We have no authority to make changes to core tables in the `public` schema.

We'll take the next-easiest approach. Let's create a new table that will function as a link table between `contact` and `icflav`, and then extend the `Contact` ORM. The good news is that when we complete this plumbing in the database, the `Contact` business object will appear in the application as if this field were in it from the beginning. Open a new file `database/source/cntcticflav.sql`:

```javascript
select xt.create_table('cntcticflav');

select xt.add_column('cntcticflav','cntcticflav_id', 'serial', 'primary key');
select xt.add_column('cntcticflav','cntcticflav_cntct_id', 'integer', 'references cntct (cntct_id)');
select xt.add_column('cntcticflav','cntcticflav_icflav_id', 'integer', 'references xt.icflav (icflav_id)');

comment on table xt.cntcticflav is 'Joins Contact with Ice cream flavor';
```

Don't forget to add this new file to the `manifest.js` file, underneath the definition for `icflav.sql`.

### ORMs

We need to extend the pre-existing `Contact` ORM to have it include `IceCreamFlavor` as a new field. By convention, ORM definitions which extend existing ORMs should go in the `database/orm/ext` directory. Make that directory and add into it a file named `contact.json`:

```javascript

{
"context": "icecream",
"nameSpace": "XM",
"type": "Contact",
"table": "xt.cntcticflav",
"isExtension": true,
"isChild": false,
"comment": "Extended by Icecream",
"relations": [
{
"column": "cntcticflav_cntct_id",
"inverse": "id"
}
],
"properties": [
{
"name": "favoriteFlavor",
"toOne": {
"type": "IceCreamFlavor",
"column": "cntcticflav_icflav_id"
}
}
],
"isSystem": true
},
{
"context": "icecream",
"nameSpace": "XM",
"type": "ContactListItem",
"table": "xt.cntcticflav",
"isExtension": true,
"isChild": false,
"comment": "Extended by Icecream",
"relations": [
{
"column": "cntcticflav_cntct_id",
"inverse": "id"
}
],
"properties": [
{
"name": "favoriteFlavor",
"toOne": {
"type": "IceCreamFlavor",
"column": "cntcticflav_icflav_id"
}
}
],
"isSystem": true
}
]
```

What we're doing here is pointing back to the original `Contact` ORM and telling it that there's another bit of data for it, in the `xt.cntcticflav` table. Note that we're adding the field both to the editable object and the list item object. That's because one drives the workspace and the other drives the list, and we're going to want both to have access to the new `favoriteFlavor` field.

### Models

We don't need to add anything to the model layer. The new field to `Contact` will be pulled up reflectively from the ORM. The link table itself has no ORM and therefore needs no model.

### The Cache

We are going to use a `XV.Picker` in the `Contact` workspace, which will rely on the Ice Cream Flavor collection to be cached in the browser. Let's set that up now, in the file `client/models/startup.js`

```javascript
XT.cacheCollection("XM.iceCreamFlavors", "XM.IceCreamFlavorCollection");
```

That was easy (don't forget reference this in the `package.js` file, underneath `ice_cream_flavor.js`!). **Verify** that this worked by refreshing the browser, opening up the Javascript console, and entering the line `XM.iceCreamFlavors`. The console should display the collection with all the flavors you added in **Part I**.

### Widgets

Next is to create a widget for the selection of `IceCreamFlavors`. In this case, we choose a `XV.Picker` over an `XV.RelationalWidget` because there will be a limited number of options and we will not need full-fledged search capabilites.

Create a new directory in `client/widgets`. The directory will need to be referenced by the root `package.js`, and it should have its own `package.js`, pointing to a new file `picker.js`, with the following contents:

```javascript
enyo.kind({
name: "XV.IceCreamFlavorPicker",
kind: "XV.PickerWidget",
collection: "XM.iceCreamFlavors"
});
```

Note that we set the collection of the picker to be the cache that we've just set up. This kind is a good illustration of the power of the way that we use Object-Oriented behavior on the client-side. All of the functionality this picker will need is shared among all pickers. The code lives in `XV.PickerWidget`. All we have to do to make a picker widget backed by this particular collection is point that general code at our new cache, and all the details will take care of themselves.

### Extending views

The last step is to add the new `IceCreamFlavorPicker` to the `Contact` workspace, by adding it into the component array. Of course, we're not allowed to change the core source of `XV.ContactWorkspace`. We have to inject it in from the extension. Luckily, our core workspaces give you an easy way to do this. We can add the following code to `client/views/workspace.js`.

```javascript
var extensions = [
{kind: "onyx.GroupboxHeader", container: "mainGroup", content: "_iceCreamFlavor".loc()},
{kind: "XV.IceCreamFlavorPicker", container: "mainGroup", attr: "favoriteFlavor" }
];

XV.appendExtension("XV.ContactWorkspace", extensions);
```

**Verify** this step by setting some flavors in with the contacts and making seeing that they stick if you back out and go back into them.

We can use the same trick to add this picker to the advanced search options for contact, by adding the following code into a new file `client/widgets/parameter.js` file.

```javascript
extensions = [
{kind: "onyx.GroupboxHeader", content: "_iceCreamFlavor".loc()},
{name: "iceCreamFlavor", label: "_favoriteFlavor".loc(),
attr: "favoriteFlavor", defaultKind: "XV.IceCreamFlavorPicker"}
];

XV.appendExtension("XV.ContactListParameters", extensions);
```

Add it to the `package.js` file underneath `picker.js`. **Verify** this step by pressing the magnifying glass icon when you're in the Contact list view, and filtering based on ice cream flavor. Only those contacts that you've set up to have that flavor should get fetched.

Congratulations! You've added the new business object to `Contact`. If you're still hungry to learn more about the capabilities of the xTuple stack, read on to [Part III](TUTORIAL3.md) to see what sorts of bells and whistles we can add to what we've built.
4 changes: 4 additions & 0 deletions docs/TUTORIAL3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## xTuple Extension Tutorial
### Part III: Bells and whistles

This still needs to be written.
1 change: 1 addition & 0 deletions sample/icecream/client/en/strings.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
var lang = XT.stringsFor("en_US", {
"_favoriteFlavor": "Favorite Flavor",
"_iceCreamFlavor": "Ice Cream Flavor",
"_iceCreamFlavors": "Ice Cream Flavors",
"_calories": "Calories"
Expand Down
3 changes: 2 additions & 1 deletion sample/icecream/client/models/package.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
enyo.depends(
"ice_cream_flavor.js"
"ice_cream_flavor.js",
"startup.js"
);
13 changes: 13 additions & 0 deletions sample/icecream/client/models/startup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*jshint indent:2, curly:true, eqeqeq:true, immed:true, latedef:true,
newcap:true, noarg:true, regexp:true, undef:true, strict:true, trailing:true,
white:true*/
/*global XT:true, XM:true, Backbone:true, _:true, console:true */

(function () {
"use strict";

XT.extensions.icecream.initStartup = function () {
XT.cacheCollection("XM.iceCreamFlavors", "XM.IceCreamFlavorCollection");
};

}());
1 change: 1 addition & 0 deletions sample/icecream/client/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ enyo.depends(
"core.js",
"models",
"en",
"widgets",
"views",
"postbooks.js"
);
7 changes: 7 additions & 0 deletions sample/icecream/client/views/workspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ white:true*/
"use strict";

XT.extensions.icecream.initWorkspace = function () {

var extensions = [
{kind: "onyx.GroupboxHeader", container: "mainGroup", content: "_iceCreamFlavor".loc()},
{kind: "XV.IceCreamFlavorPicker", container: "mainGroup", attr: "favoriteFlavor" }
];
XV.appendExtension("XV.ContactWorkspace", extensions);

enyo.kind({
name: "XV.IceCreamFlavorWorkspace",
kind: "XV.Workspace",
Expand Down
4 changes: 4 additions & 0 deletions sample/icecream/client/widgets/package.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
enyo.depends(
"picker.js",
"parameter.js"
);
18 changes: 18 additions & 0 deletions sample/icecream/client/widgets/parameter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*jshint node:true, indent:2, curly:true, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
regexp:true, undef:true, trailing:true, white:true */
/*global XT:true, XV:true, enyo:true, _:true */

(function () {

XT.extensions.icecream.initParameterWidget = function () {

var extensions = [
{kind: "onyx.GroupboxHeader", content: "_iceCreamFlavor".loc()},
{name: "iceCreamFlavor", label: "_favoriteFlavor".loc(),
attr: "favoriteFlavor", defaultKind: "XV.IceCreamFlavorPicker"}
];

XV.appendExtension("XV.ContactListParameters", extensions);
};

}());
16 changes: 16 additions & 0 deletions sample/icecream/client/widgets/picker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*jshint node:true, indent:2, curly:true, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
regexp:true, undef:true, trailing:true, white:true */
/*global XT:true, XM:true, enyo:true, _:true */

(function () {

XT.extensions.icecream.initPicker = function () {

enyo.kind({
name: "XV.IceCreamFlavorPicker",
kind: "XV.PickerWidget",
collection: "XM.iceCreamFlavors"
});
};

}());
52 changes: 52 additions & 0 deletions sample/icecream/database/orm/ext/contact.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[
{
"context": "icecream",
"nameSpace": "XM",
"type": "Contact",
"table": "xt.cntcticflav",
"isExtension": true,
"isChild": false,
"comment": "Extended by Icecream",
"relations": [
{
"column": "cntcticflav_cntct_id",
"inverse": "id"
}
],
"properties": [
{
"name": "favoriteFlavor",
"toOne": {
"type": "IceCreamFlavor",
"column": "cntcticflav_icflav_id"
}
}
],
"isSystem": true
},
{
"context": "icecream",
"nameSpace": "XM",
"type": "ContactListItem",
"table": "xt.cntcticflav",
"isExtension": true,
"isChild": false,
"comment": "Extended by Icecream",
"relations": [
{
"column": "cntcticflav_cntct_id",
"inverse": "id"
}
],
"properties": [
{
"name": "favoriteFlavor",
"toOne": {
"type": "IceCreamFlavor",
"column": "cntcticflav_icflav_id"
}
}
],
"isSystem": true
}
]
8 changes: 8 additions & 0 deletions sample/icecream/database/source/cntcticflav.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
select xt.create_table('cntcticflav');

select xt.add_column('cntcticflav','cntcticflav_id', 'serial', 'primary key');
select xt.add_column('cntcticflav','cntcticflav_cntct_id', 'integer', 'references cntct (cntct_id)');
select xt.add_column('cntcticflav','cntcticflav_icflav_id', 'integer', 'references xt.icflav (icflav_id)');

comment on table xt.cntcticflav is 'Joins Contact with Ice cream flavor';

1 change: 1 addition & 0 deletions sample/icecream/database/source/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"comment": "Ice Cream Flavor sample extension",
"databaseScripts": [
"icflav.sql",
"cntcticflav.sql",
"register.sql"
]
}

0 comments on commit c437b6a

Please sign in to comment.