Skip to content
This repository has been archived by the owner on Jul 7, 2020. It is now read-only.

Commit

Permalink
All exercises and code
Browse files Browse the repository at this point in the history
  • Loading branch information
Per Ploug Krogslund committed Jun 13, 2014
1 parent 1700967 commit 763be0e
Show file tree
Hide file tree
Showing 2,924 changed files with 440,441 additions and 3 deletions.
31 changes: 31 additions & 0 deletions Exercises/Exercise1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#Exercise 1: Our very first property editor
This exercise will result in a basic property editor, that will show you how to connect your editor with Umbraco on the serverside, and how to populate and sync data in the UI.

##The manifest
Create a new folder at /app_plugins/first/, inside this folder, create a new file and call it package.manifest.

This file is used to, among other things,register property editors. So we will add a bit of json to describe what we are adding:

{
propertyEditors:[
{
name: "My editor",
alias: "my.editor",
editor:{
view: "~/app_plugins/first/editor.html"
}
}
]
}


So we create a propertyEditors array, which we then add a single editor to. This editor has basic meta data like name, alias and most importantly, the path to the html that will show in the Ui when loaded.

##Editing the view
Let’s add that view, which is a .html file at `/app_plugins/first/editor.html`, then Open the editor.html file, and add nothing but:

<input type="text" ng-model="model.value" />

Now, lets restart the application, by opening the `web.config` and make an edit like a linebreak or space to “touch it”. Every time we change the manifest, we must make the application restart to pickup the changes.

After restart, create a new data type named “**glass picker**”, and choose “**My editor**” from the list of available editors, add that data type to the “drink” document type as a property with the alias “**glass**” and open a page of that type.
16 changes: 16 additions & 0 deletions Exercises/Exercise10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#Exercise 10: reuse glass picker as parameter editor
Remember that glass editor we made as the very first thing? - lets go back and reuse it as a macro parameter editor, in case we ever want to have a cocktail glass image path as a macro parameter (happens all the time).

##Edit the /app_plugins/first/package.manifest
Just add a isParameterEditor value to the propertyEditor definition, and after a app restart, it will show up in the list of macro parameter editors.

propertyEditors:[
{
name: "My first editor",
alias: "My.First",
isParameterEditor: true,
editor:{
view: "~/app_plugins/first/editor.html"
}
}
]
64 changes: 64 additions & 0 deletions Exercises/Exercise11.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#Exercise 11: add a chart
Final exercise of the day, which involves pie-charts. First of all, copy __/help/exercise11-start/highcharts.min.js__ into /app_plugins/ingredient/.

Open __/app_plugins/ingredient/editor.controller.js__ and using a core Umbraco service called assetsService, we can lazy load any javascript file, and at the same time pause execution till its fully loaded, since the loading returns a “promise”:

##Loading external asset

assetsService.loadJs("/app_plugins/ingredient/highcharts.min.js")
.then(function(){
<<<< all the $scope functions already added, >>>>
});

Like anything else in Angular, this service must be injected in the controllers function.

That means, when the code inside .then() executes, the file is already loaded, and we can safely reference any of its functions. This is exactly what we will do, to instantiate a pie chart based on our drink ingredients:

##Rendering a chart

$scope.renderChart = function () {
//collect an array from model.value
var data = [];

_.forEach($scope.model.value, function (ing) {
//only add if ingredient has a .percentage
if (ing.percentage) {
data.push([ing.name, ing.percentage]);
}
});

//we find an element with ID 'container" and add the chart data to.
$('#container').highcharts({
series: [{
type: 'pie',
data: data
}]
});
};

Every time we call __$scope.renderChart()__; the chart will be built based on our current set of ingredients.

##Update the view
We need 2 more things, adding a percentage form input in the table, and the div for displaying the chart.

For the form input we use a `<input type=”number” />` to ensure we get an interger, as highcharts requires, add it inside the model.value loop in the ingredient table.

<input type="number" class="span2" ng-model="ingredient.percentage"/>

Then we add a basic div to load the charts into (required by highcharts) just below the ingredient table:

<div id="container" style="height: 400px; width: 600px;"></div>

Try calling __$scope.renderChart()__ and see what happens.

##Trigger chart on changes to model.value
Instead of manually calling renderChart() or making it rely on user action, we can instead
use $scope.$watch to monitor if a value on the scope changes.

$scope.$watch("model.value",
function(newIngredients, oldIngredients) {
$scope.renderChart();
},
true);

Attach a browser debugger inside the $watch state to monitor when it’s triggered by changes and additions to the ingredients list.
97 changes: 97 additions & 0 deletions Exercises/Exercise2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#Exercise 2: Our first controller
We continue to work with the first.html file and will now add a controller to our view to introduce some more logic to our editor.

##The controller setup
Add a new javascript file at __/app_plugins/first/editor.controller.js__ - open it, and add a basic controller declaration to it like so:

angular.module("umbraco").controller("My.EditorController", function(){
alert("Here I am");
});

##The view setup
We also need to tell the editor html view to actually load and use the controller we’ve setup, so modify the editors html to be like so:

<div ng-controller="My.EditorController">
<input type="text" ng-model="model.value" />
</div>

##The manifest
We need to tell our application to actually load this controller.js file on application start, so open /app_plugins/first/package.manifest and add a javascript array to the configuration, like so:

{
propertyEditors:[
{
}
],
javascript: [
"~/app_plugins/first/editor.controller.js"
]
}

Restart the application - you will now see an alert box when you reload the page with the editor on.

##$Scope
Now lets work with some data in our scope. First of all, inject the $scope, by adding it to the controllers function like so:

angular.module("umbraco").controller("My.EditorController", function($scope){
alert("Here I am");
});

This gives us access to work with data inside of the scope of the html element that’s calling the controller.

Add a simple array of glasses to the $scope like so:

$scope.glasses = [
{
name: "cocktail",
image: "/images/glasses/cocktail.jpg"
},
{
name: "shot",
image: "/images/glasses/shot.jpg"
},
{
name: "highball",
image: "/images/glasses/highball.jpg"
},
];

The images/glass files have already been added to the web site.

##Ng-repeat
We will now bind the data from the glasses array to our html, switch over to the “editor.html” view and replace the <input /> element with an un-ordered list like below:

<ul class="thumbnails">
<li class="span3" ng-repeat="glass in glasses">
<a href class="thumbnail">
<img ng-src="{{glass.image}}" />
<small>{{glass.name}}</small>
</a>
</li>
</ul>
Notice how each item in the array are bound to the html, this is one of the most common ways to do templating in Angular.

##Ng-Click
Modify the `<a>` element inside the list. and add a ng-click attribute to it like so:

… <a href class="thumbnail" ng-click="choose(glass)"> …

This means that when the link is clicked, it will try to execute a method called “choose” on the $scope. so lets add this method to the editor.controller.js file like so:

$scope.choose = function(glass){
$scope.model.value = glass.image;
};

This will store the glass image on the property, and now be available to use in the drink template, so we can replace with hardcoded glass image reference with dynamic data.

This is the standard way of calling methods in a controller from a view in Angular.

##Ng-Class
Lets try one more thing with templating, indicate what item is currently selected. We can do this by applying the class “selected” to the <li> element that is currently picked.

In angular, we can apply classes based on conditions, by adding an ng-class attribute to the element like so:

<li ng-class="{selected: glass.image === model.value}">

This performs a simple expression to determine if the class should be applied or not, in this case, it will return true if the image path is equal to the value on model.value.
91 changes: 91 additions & 0 deletions Exercises/Exercise3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#Exercise 3: Adding a service
Its time to look at another central Angular concept: the service. We will in this exercise create a service to fetch cocktail ingredients and list them out in an editor. The whole idea of using a service is to centralize and share our code, so this service will be used in multiple places as the workshop progresses.

__Note:__ The angular term is __“service”__, but in Umbraco we label all services that access the database as a __“resource”__ - and to make it even more confusing, we implement it using `angular.factory`.

##Copy the “first” folder
Copy the “/app_plugins/first” folder and paste it as “/app_plugins/ingredient” - we could have kept it in the first folder, but it’s also nice to keep things tidy.

##The manifest
Open the “/app_plugins/ingredient/package.manifest” file and modify it so the previously named “first editor” is now called “ingredients editor” like so:

{
propertyEditors:[
{
name: "Ingredient editor",
alias: "my.ingredient.editor",
editor:{
view: "~/app_plugins/ingredient/editor.html",
valueType: "JSON"
}
}
],
….
}

__Notice:__ we also added __“valueType”__ - this makes umbraco store data from the editor as json, and will serialize it when we later access it in the template.

Also, we need to modify the javascript array so it includes 2 files, the ingredient.resource.js will be added in the next exercise

javascript: [
"~/app_plugins/ingredient/editor.controller.js",
"~/app_plugins/ingredient/ingredient.resource.js"
]

##Create the service file
Now create /app_plugins/ingredient/ingredient.resource.js and make a basic service registration like so:

angular.module("umbraco.resources")
.factory("ingredientResource", function($http){

var myService = {};
return myService;
});

##GetAll() method
Now, lets make the ingredient service call our server for some data. We use $http which is included in Angular.

myService.getAll = function(){
return $http.get("Backoffice/Ingredient/IngredientApi/getAll");
};


This simply exposes our ajax call to the server as a simple API, which we could later change the actual implementation of, if we wanted to.

Modify the editor view and controller to list the ingredients
Open /ingredient/editor.controller.js and inject the ingredientResource into the controller, by adding it to the controller function() like so:

angular.module("umbraco")
.controller("My.IngredientController",
function($scope, ingredientResource){
$scope.ingredients = ingredientResource.getAll();
});

also, as you can see we modify the name of the controller, Switch to the html view, and list out the ingredients as a list:

<div ng-controller="My.IngredientController">
<ul>
<li ng-repeat="ingredient in ingredients.data">
{{ingredient.name}}
</li>
</ul>
</div>

__Hint__: to see all the data in ingredients, use `{{ingredients | json}}``

We now have a central location where we can attach all our ingredient handling code, so we don’t have to keep this in different controllers, anywhere we will need access to this service, we simply inject ingredientResource.

##Testing
To test all this, restart the application, add a new data type called “ingredient picker” and select “Ingredient editor” as its editor.
Then add it to the drink document type as a property with the alias “ingredients”

If everything is in order, you will see a list of drink ingredients like so:

- Campari
- Aperol
- Dry vermouth
- Sweet vermouth
- Rose vermouth
- Orange peel
- Gin
- Vodka
74 changes: 74 additions & 0 deletions Exercises/Exercise4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#Exercise 4: Connect editor and service
Now that we have a simple editor running, and can pull in data from our service, its time to really make it do some work. In this exercise we will enable the user to pick ingredients from the service, enable search for ingredients, and store all the ingredients data in the Umbraco cache, for easy use on the drinks page.

##Setup search and type-ahead
Instead of using a long list of ingredients to pick from, we will use a simple type-ahead directive to search for needed ingredients. So first of all we need to make the ingredientResource search ingredients by name:

Add the following method to ingredient.resource.js:

myService.search = function(name){
return $http.get("Backoffice/Ingredient/IngredientApi/getByName?name=" +
name);
};

Then we need to wire up our view and controller to call the search method, first add a controller method to populate the list of ingredients found:

$scope.find = function(event, term){
$scope.ingredients = ingredientResource.search(term);
}

we then add a textbox to call this method as we type:

<input type="text"
ng-keyup="find($event, searchTerm)"
ng-model="searchTerm" />

Reload the page, and see how the list updates as you type.

Now that we have a way to search for ingredients, we need to store them on $scope.model.value to persist them to the document.

To do this, we add a select() method which we can then wire up to the list of ingredients with ng-click:

if(!angular.isArray($scope.model.value)){
$scope.model.value =[];
}
$scope.select = function(ingredient){
$scope.model.value.push(ingredient);
};

Notice we also ensure that the model.value is an array we can push data to incase its not defined.

To use the $scope.select function from our view, we wrap the ingredient label in a link with an ng-click attribute:

<li ng-repeat="ingredient in ingredients.data">
<a ng-click="select(ingredient)">
{{ingredient.name}}
</a>
</li>

##Displaying selected ingredients
Finally we need a table to list the selected values:

<table>
<tr ng-repeat="ingredient in model.value">
<td><input type="text" ng-model="ingredient.quantity"/></td>
<td>
{{ingredient.name}}
</td>
</tr>
</table>

Notice how we also add a quantity field to the ingredient object, try populating this and see how its included in the saved data. (use {{model.value | json }} for a quick raw view - or use the browser debugging tools.

##Removing selected values
Last thing we need in the ingredient editor, is to be able to remove selected ingredients from the table. First we add a link to the ingredient row:

<a href ng-click="remove($index)">Remove</a>

we then need to add this remove method to the controller:

$scope.remove = function(index){
$scope.model.value.splice(index, 1);
}

Don’t worry if your editor looks like crap while working on it, we are just prototyping, but just in case, the __/help/exercise4__ files has a prettified html view
Loading

0 comments on commit 763be0e

Please sign in to comment.