This repository has been archived by the owner on Jul 7, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Per Ploug Krogslund
committed
Jun 13, 2014
1 parent
1700967
commit 763be0e
Showing
2,924 changed files
with
440,441 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.