{"id":16721636,"url":"https://github.com/zoltan-nz/product-app","last_synced_at":"2025-04-09T06:13:13.653Z","repository":{"id":144596616,"uuid":"64136205","full_name":"zoltan-nz/product-app","owner":"zoltan-nz","description":"New intermediate level Ember.js Octane v3.17+ tutorial in README","archived":false,"fork":false,"pushed_at":"2025-03-28T22:16:10.000Z","size":4593,"stargazers_count":92,"open_issues_count":1,"forks_count":28,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-03-30T13:03:43.604Z","etag":null,"topics":["ember","ember-guide","ember-tutorial","tutorial"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zoltan-nz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-07-25T13:21:15.000Z","updated_at":"2024-09-25T08:19:37.000Z","dependencies_parsed_at":null,"dependency_job_id":"649acd6a-e6aa-4fea-8569-62de8e98129c","html_url":"https://github.com/zoltan-nz/product-app","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoltan-nz%2Fproduct-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoltan-nz%2Fproduct-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoltan-nz%2Fproduct-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoltan-nz%2Fproduct-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zoltan-nz","download_url":"https://codeload.github.com/zoltan-nz/product-app/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247987285,"owners_count":21028895,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ember","ember-guide","ember-tutorial","tutorial"],"created_at":"2024-10-12T22:31:38.789Z","updated_at":"2025-04-09T06:13:13.632Z","avatar_url":"https://github.com/zoltan-nz.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Product App\n\nUpdated to the latest Ember v3\n\nWe are going to build a web application which could be a web-shop or a product management app. We can add products, they can belong to a category. We can manage categories also. First of all let's focus on the administration area, later we build the \"store front\" where a user can collect products in a shopping cart.\n \nI suppose, you finished already the [Ember.js Tutorial][yoember.com], where you built the Library App, so you know roughly how we build Ember application on \"Ember Way\". For this reason, I don't explain everything, we can focus only the main steps. (It means, you will see all the steps with less explanation. ;)\n\nAt the beginning there won't add any styling, so we can focus only for the functionality.\n \nLet's create a new app:\n\n```shell\n$ ember new product-app\n```\n\nThis is the data model structure, what we would like to implement. I leave it here, so I can refer back to this table when we implement the related part.\n\n## Models\n\n|`category`||\n|---|---\n|`name`|string\n|`products`|hasMany\n\n|`product`||\n|---|---\n|`name`|string\n|`sku`|string\n|`categories`|belongsTo\n|`tags`|hasMany\n|`unitPrice`|number\n\n|`tag`||\n|---|---\n|`name`|string\n|`products`|hasMany\n\n|`shoppingCart`||\n|---|---\n|`user`|string\n|`purchaseDate`|date\n|`paid`|boolean\n|`lineItems`|hasMany\n|`total`|number\n\n|`lineItem`||\n|---|---\n|`shoppingCart`|belongsTo\n|`product`|belongsTo\n|`quantity`|number\n|`sum`|number\n\n## Requirements\n\nLet's create a list about our requirements. Basically this will be our main todo list. ;)\n\n* [x] [Lesson 1 - Admin user can navigate to `/admin`](#user-content-admin-page)\n* [x] [Lesson 2 - Admin user can CRUD (create, read, update, delete) `categories` on `/admin/categories`](#user-content-categories)\n* [x] [Homework 1 - Admin user can CRUD `products` on `/admin/products`](#user-content-homework-1)\n* [x] [Lesson 3 - Admin user can change the `category` of a `product`](#user-content-relationship)\n* [ ] [Homework 2 - User can see the list of products on the `home` (`index`)](#user-content-homework-2)\n* [ ] [Lesson 4 - User can filter the list of products clicking on a `category`](#user-content-filter)\n* [ ] [Lesson 5 - User can collect products in a shopping cart](#user-content-shopping-cart)\n\n##\u003ca name='admin-page'\u003e\u003c/a\u003e 1. Home page and Admin page\n\n### Creating `application` template and a link to the home page\n\n```shell\n$ ember g template application\n```\n\n```handlebars\n{{!-- app/templates/application.hbs --}}\n{{link-to 'Home' 'index'}}\n\n\u003chr\u003e\n\n{{outlet}}\n```\n\nAdd an index page with a header.\n\n```shell\n$ ember g template index\n```\n\n```handlebars\n{{!-- app/templates/index.hbs --}}\n\u003ch1\u003eHome Page\u003c/h1\u003e\n```\n\n### Creating `/admin` route\n\n* Create an `admin` route, \n* add an `h1` header to the main admin page and \n* add a link to the `application` template.\n\n```shell\n$ ember g route admin\n```\n\n```handlebars\n{{!-- app/templates/admin.hbs --}}\n\u003ch1\u003eAdmin Page\u003c/h1\u003e\n\n{{outlet}}\n```\n\n```handlebars\n{{!-- app/templates/application.hbs --}}\n{{link-to 'Home' 'index'}} | {{link-to 'Admin' 'admin'}}\n```\n![Home page and Admin page][step_1]\n\n## \u003ca name='categories'\u003e\u003c/a\u003e2. Categories Page and CRUD interface\n\n### `admin/categories` page\n\n* Create a `categories` page under `admin` route and \n* add a link to the main admin page.\n\n```shell\n$ ember g route admin/categories\n```\n\n```handlebars\n{{!-- app/templates/admin/categories.hbs --}}\n\u003ch1\u003eCategories Admin Page\u003c/h1\u003e\n\n{{outlet}}\n```\n\n```handlebars\n{{!-- app/templates/admin.hbs --}}\n\u003ch1\u003eAdmin Page\u003c/h1\u003e\n\n{{link-to 'Categories' 'admin.categories'}}\n\n\u003chr\u003e\n\n{{outlet}}\n```\n![Categories subroute][step_2_1]\n\n## Notes about nested templates and directory structure\n\n![Nested Template Structure][nested-template]\n\nIn Ember, the templates have a clear, strict hierarchy. Firstly, each page or subpage (embeded page) has a main \"wrapper\" template and has an \"index\" page. For example, the main-main, top template is the `application.hbs`, and it has an `index.hbs`, which actually the app's home page. \n\nThe \"wrapper\" page file name is the same as the represented route, so if we have an `/admin` page, than we have an `admin` route, so we have an `admin.hbs`. If we wouldn't like to add a nested subroute to `admin` we can use this `admin.hbs` for presenting the content. Otherwise if we add a new subroute to the admin, for example `/admin/categories`, than we have to create a new folder in the `templates` directory, this new folder will be `templates/admin`. In this folder we can have an `index.hbs`, which will be the main page of the `/admin` route, and we could have a `categories.hbs` which will be the \"wrapper\" file for that subroute.\n\nImportant, if we have a subroute, we have to add `{{outlet}}` handlebar code to the \"wrapper\" template. The subroute content will be rendered in this \"outlet\" placeholder. \n\n## Creating the `Category` model\n\n**Repeat**: Please learn *computed properties*, *observers* and *actions* from Lesson 2 on [Ember][yoember.com] Tutorial.\n\nIn this session we add real functionality to our Category Admin page. Firstly, we implement an input box with an \"Add\" button, which will extend the category list, additionally we attache a \"Delete\" button also to each existing category item, so we can remove them from the list. In our first implementation it will use only an Ember Array, so it uses the web-browser memory only. Secondly, we will use Ember Data and a proper Model, which would expect the existence of a database and a backend system. Luckily we can mock the backend. I show two options here, the first will use [Ember CLI http-mock server][ember_cli_mock_server], the second one will use a popular add-on: [Ember Mirage][ember_mirage].\n\n### Data down from route to template\n\n**Important**: In Ember.js everything starts from the url. When you navigate to a page, the url changes, Ember automatically checks the map in `router.js`. Based on the `Router.map` it is automatically enter in the connected Route Handler (route). Ember will goes through on a certain steps in this route, after it will setup the controller and finally the template.  \n\nCheck out this figure from the [Ember Guides][ember_guide]\n\n![Ember Application concept][ember_concept_image]\n\nThe rule of thumb, if you would like to show data from an external source (from your database) on your page, it should download (via backend service) almost always in `model` hook of the route handler. Which means, you almost always have to have a `model` function in  your route file and return the data from that function, it will automatically added to a `model` property in your controller and template. Let see, how it works in our Category Admin page.\n\nWe already generated our Categories route handler (`app/routes/admin/categories.js`). Let's extend this file with a `model` function, with a \"model hook\". We call it \"model hook\", because this function is exists in the Ember framework, so it will be automatically invoked. Check out in the [official documentation][route_handler_api] how many \"built-in\" functions are in a route handler, but don't worry, we will use only a couple, if you are already on the official api documentation page, please read the doc of the `model` hook with clicking on the \"model\" link. If it does not make any sense, you are not alone. It is totally normal, when you start learning a new framework or tool. ;) It will be much clearer later. \n\nBack to our Product App. Update the category route. Let's return an array of objects in our \"model hook\".\n\n```js\n// app/routes/admin/categories.js\nimport Ember from 'ember';\n\nexport default Ember.Route.extend({\n\n  model() {\n    return [\n      {\n        id: 1,\n        name: 'First Category'\n      },\n      {\n        id: 2,\n        name: 'Second Category'\n      }\n    ];\n  }\n  \n});\n```\n\nNow, update the categories template, add an `each` loop handlebars helper to `categories.hbs`:\n\n```hbs\n\u003ch1\u003eCategories Admin Page\u003c/h1\u003e\n\n\u003cul\u003e\n  {{#each model as |category|}}\n    \u003cli\u003eID: {{category.id}}, NAME: {{category.name}}\u003c/li\u003e\n  {{/each}}\n\u003c/ul\u003e\n```\nYey, we have a list of categories:\n\n![List categories][step_2_2]\n\nNext step is creating an input field and adding new items to our model. I suppose, you already know a lot about [actions][actions_official_guide] also.\n\nUpdate your template with a form, an input box with action, and let's add a counter also:\n\n```hbs\n{{!-- /app/templates/admin/categories.hbs --}}\n\u003ch1\u003eCategories Admin Page\u003c/h1\u003e\n\n\u003cform\u003e\n  \u003clabel\u003eID:\u003c/label\u003e\n    {{input value=newCategoryId}}\n  \u003clabel\u003eNAME:\u003c/label\u003e\n    {{input value=newCategoryName}}\n  \u003cbutton type=\"submit\" {{action 'addNewCategory' newCategoryId newCategoryName}}\u003eAdd\u003c/button\u003e\n\u003c/form\u003e\n\n\u003cul\u003e\n  {{#each model as |category|}}\n    \u003cli\u003eID: {{category.id}}, NAME: {{category.name}}\u003c/li\u003e\n  {{/each}}\n\u003c/ul\u003e\n\nCategory Counter: {{model.length}}\n```\nSo we have a simple form, where we read an `id` and a `name`, we can submit this data with hitting Enter or clicking on the button. It will invoke the action function and pass two params.\n\nWe have to implement the action in our route handler. This action will push a new object to the `model` array, which is in the controller.\n\n```js\n// app/routes/admin/categories.js\nimport Ember from 'ember';\n\nexport default Ember.Route.extend({\n\n  model() {\n    return [\n      {\n        id: 1,\n        name: 'First Category'\n      },\n      {\n        id: 2,\n        name: 'Second Category'\n      }\n    ];\n  },\n\n  actions: {\n\n    addNewCategory(id, name) {\n      this.controller.get('model').pushObject({ id, name });\n    }\n\n  }\n});\n```\nAdd the delete button also, extend the `categories.hbs` template list element with a button:\n\n```hbs\n\u003cul\u003e\n  {{#each model as |category|}}\n    \u003cli\u003e\n      ID: {{category.id}}, NAME: {{category.name}} \n      \u003cbutton {{action 'deleteCategory' category}}\u003eDel\u003c/button\u003e\n    \u003c/li\u003e\n  {{/each}}\n\u003c/ul\u003e\n```\nAction goes in `app/routes/admin/categories.js`:\n\n```js\n//...\n  actions: {\n\n    addNewCategory(id, name) {\n      this.controller.get('model').pushObject({ id, name });\n    },\n\n    deleteCategory(category) {\n      this.controller.get('model').removeObject(category);\n    }\n  }\n//...\n```\nYou can read more about `pushObject` and `removeObject` on `Ember.NativeArray` [documentation page][native_array_doc].\n\nIs your app looks like this?\n\n![Form with Add and Del buttons][step_2_3]\n\nBrilliant, you can add and remove items from an array model, however if you reload the page, all added record is gone.\n\n### Ember Data\n\nEmber Data is responsible for managing ajax request from or to a server. It uses adapters to communicate with different type of backend systems.\n\nAn Ember application has a `store` service. We can access data via this service.\n\nThe core element of Ember Data is Ember Model, where we can declare the properties of a model.\n\n[More about models on the official guide][official_guide_models] and architecture overview from this page:\n\n![Ember store architecture overview][ember_store_image]\n\n**Generate Model**\n\nEmber CLI can generate for us a skeleton model. The following command will create a `category.js` in our `model` folder and will add a `name` field with `string` type.\n\n```shell\n$ ember generate model category name:string\n```\n\n**Update model hook**\n\nWe have a model in our Product Application, let's use it in our Categories admin page. \n\nLet's delete the dummy data from `model()` hook in `/routes/admin/categories.js` and update as follow. In the same step, we can update our `addNewCategory()` and `deleteCategory()` actions also.\n \n```js\n// app/routes/admin/categories.js\nimport Ember from 'ember';\n\nexport default Ember.Route.extend({\n\n  model() {\n    return this.store.findAll('category');\n  },\n\n  actions: {\n\n    addNewCategory(id, name) {\n      this.store.createRecord('category', { id, name }).save();\n    },\n\n    deleteCategory(category) {\n      category.destroyRecord();\n    }\n  }\n});\n```\n\nWe use `this.store.findAll()` for downloading all the available record, `this.store.createRecord()` can create a new record, `.save()` would try to permanently save it. We can use `.destroyRecord()` for totally remove from our app and from the server the related record.\n\nBut first of all try out the above code. Try to **reload** the page.\n\n*Check the console!* Our app try to download data from somewhere, but get a 404 Error response, because we doesn't really have any backend server.\n\nYour backend could be Ruby on Rails app, Node.js app, .Net app, PHP app, Python based app, Elixir or anything else. It could be a cloud based solution also, like Firebase, you've already learned about it when you built the Library App from http://yoember.com.\n\nIn this tutorial, we will use the famous [Ember-Mirage][ember_mirage] mock server.\n\n### Add Mirage\n\n1. Install Mirage\n\n```\n$ ember install ember-cli-mirage\n```\n\nCheck our new helpers:\n\n```\n$ ember g --help\n```\n\n\n2. Create a Mirage Model, we would like to mock our Category:\n\n```\n$ ember g mirage-model category\n```\n\nUpdate `mirage/config.js`\n\n```js\nthis.namespace = '/api';\n\nthis.get('/categories', (schema, request) =\u003e {\n  return schema.categories.all();\n});\n```\n\n* Relaunch your app, try to click on \"Categories\".\n\n* Check the error message in console.\n\nActually, you can play with the old `jQuery.get()` in console.\n`$.get('/api/categories')`\n\n3. Time to add Adapter to our Ember app:\n\n```\n$ ember g adapter application\n```\n\nStill need the `namespace` setting.\n\n```js\n// app/adapters/application.js\nimport DS from 'ember-data';\n\nexport default DS.JSONAPIAdapter.extend({\n\n  namespace: 'api'\n});\n```\n\nTry now!\n\n4. Fake data with Factories\n\n```\n$ ember g mirage-factory category\n```\n\n```js\n// mirage/factories/category.js\nimport { Factory } from 'ember-cli-mirage';\n\nexport default Factory.extend({\n\n  name(i) {return `Category ${i}`}\n\n});\n```\n\nLet's update our default scenario:\n\n```js\n// mirage/scenarios/default.js\nexport default function(server) {\n\n  server.createList('category', 10);\n}\n```\n\nCheck your app.\n\nUpdate `mirage/config.js` with shorthand\n\n```js\nthis.get('/categories');\n```\n\nTry to save a new category...\n\nCheck console.\n\nExtend config\n\n```js\nthis.post('/categories');\n```\n\nTry to delete a new category...\n\nCheck console.\n\nExtend config\n\n```js\nthis.del('/categories/:id');\n```\n\nUsing Faker in Factory\n\nCheck [faker.js][faker_js]\n\n```js\n// mirage/factories/category.js\nimport { Factory, faker } from 'ember-cli-mirage';\n\nexport default Factory.extend({\n\n  name: faker.commerce.department\n});\n```\n\nOne more!\n\nReplace `get`, `post` and `del` with a single `resource` shorthand:\n\n```js\nthis.resource('categories');\n```\n\n### Add Bootstrap\n\nIn an earlier implementation, this repository used [`ember-bootstrap`][ember_bootstrap], but I felt it was a little bit too abstract. The latest version of this project uses [`ember-cli-bootstrap-sassy`][ember-cli-bootstrap-sassy].\n\nMore details: http://yoember.com/#ember-bootstrap-sass\n\n```\n$ ember install ember-cli-sass\n$ ember install ember-cli-bootstrap-sassy\n$ ember install ember-bootstrap-nav-link\n$ echo '@import \"bootstrap\";' \u003e ./app/styles/app.scss \u0026\u0026 rm ./app/styles/app.css\n```\n\napplication.hbs\n```handlebars\n\u003cnav class=\"navbar navbar-inverse navbar-fixed-top\"\u003e\n  \u003cdiv class=\"container\"\u003e\n    \u003cdiv class=\"navbar-header\"\u003e\n      \u003cbutton type=\"button\" class=\"navbar-toggle collapsed\" data-toggle=\"collapse\" data-target=\"#navbar-collapse\"\n              aria-expanded=\"false\"\u003e\n        \u003cspan class=\"sr-only\"\u003eToggle navigation\u003c/span\u003e\n        \u003cspan class=\"icon-bar\"\u003e\u003c/span\u003e\n        \u003cspan class=\"icon-bar\"\u003e\u003c/span\u003e\n        \u003cspan class=\"icon-bar\"\u003e\u003c/span\u003e\n      \u003c/button\u003e\n      {{link-to 'Product App' 'index' class=\"navbar-brand\"}}\n    \u003c/div\u003e\n\n    \u003cdiv class=\"collapse navbar-collapse\" id=\"navbar-collapse\"\u003e\n\n      \u003cul class=\"nav navbar-nav\"\u003e\n\n        \u003cNavLinkTo @route='index'\u003eHome\u003c/NavLinkTo\u003e\n        \u003cNavLinkTo @route='admin'\u003eAdmin\u003c/NavLinkTo\u003e\n\n      \u003c/ul\u003e\n\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/nav\u003e\n\n\u003cdiv class=\"container\"\u003e\n  {{outlet}}\n\u003c/div\u003e\n```\n\napp.scss update\n\n```\nbody {\n  padding-top: 70px\n}\n```\n\nadmin.hbs\n\n```\n\u003cul class=\"nav nav-pills\"\u003e\n  \u003cNavLinkTo @route='admin.categories'\u003eCategories\u003c/NavLinkTo\u003e\n  \u003cNavLinkTo @route='admin.products'\u003eProducts\u003c/NavLinkTo\u003e\n\u003c/ul\u003e\n\n\u003chr\u003e\n\n{{outlet}}\n```\n\n### Save Category record to database\n\nWe can update now our `addNewCategory` action in `app/routes/admin/categories.js`.\n\nBecause the `id` of the record is generated by the backend, in our case our mock database system, we can remove this param from our function and from our template also. We can use Ember Bootstrap components for building our form.\n\n```handlebars\n{{!-- app/templates/admin/categories.hbs --}}\n\u003ch1\u003eAdmin - Categories\u003c/h1\u003e\n\n\u003cdiv class=\"well well-sm\"\u003e\n\n  \u003cform class=\"form-inline\" {{action 'addNewCategory' newCategoryName on='submit'}}\u003e\n    \u003cdiv class=\"form-group\"\u003e\n      \u003clabel for=\"new-category\"\u003eNew category:\u003c/label\u003e\n      {{input type=\"text\" class=\"form-control\" id=\"new-category\" placeholder=\"Category name\" value=newCategoryName}}\n    \u003c/div\u003e\n    \u003cbutton type=\"submit\" class=\"btn btn-default\"\u003eAdd\u003c/button\u003e\n  \u003c/form\u003e\n\u003c/div\u003e\n\n\u003ctable class=\"table table-striped\"\u003e\n  \u003ccaption\u003eList of categories\u003c/caption\u003e\n  \u003cthead\u003e\n  \u003ctr\u003e\n    \u003cth\u003e#\u003c/th\u003e\n    \u003cth\u003eName\u003c/th\u003e\n    \u003cth\u003eActions\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n  {{#each model as |category|}}\n    \u003ctr\u003e\n      \u003ctd\u003e{{category.id}}\u003c/td\u003e\n      \u003ctd\u003e{{category.name}}\u003c/td\u003e\n      \u003ctd\u003e\n        \u003cbutton class=\"btn btn-xs btn-danger\" {{action 'deleteCategory' category}}\u003eDel\u003c/button\u003e\n      \u003c/td\u003e\n    \u003c/tr\u003e\n  {{/each}}\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\nNumber of categories: {{model.length}}\n```\n\n```js\n// app/routes/admin/categories.js\nimport Ember from 'ember';\n\nexport default Ember.Route.extend({\n\n  model() {\n    return this.store.findAll('category');\n  },\n\n  actions: {\n\n    addNewCategory(name) {\n      this.store.createRecord('category', { name }).save();\n    },\n\n    deleteCategory(category) {\n      category.destroyRecord();\n    }\n  }\n});\n```\n\nThere is a `.save()` which is a `Promise`, so we can use `.then` to manage the callback, when the server respond arrives.\n\n```js\n.then(\n  category =\u003e console.log('Response:', category),\n  error =\u003e console.log(error)\n)\n```\n\nServer respond can be positive or negative. It can \"fulfill\" or \"reject\" our request. The `.then()` method has two params, both are callback functions. The first will get the positive respond with the record, the second will get an error respond. You can play with it with changing our mock server (mirage) settings. Mirage can simulate negative responds also.\n\nYou can read more about mirage's route handler and post shorthands on the following pages:\nhttp://www.ember-cli-mirage.com/docs/v0.2.x/route-handlers/\nhttp://www.ember-cli-mirage.com/docs/v0.2.x/shorthands/#post-shorthands\n\nIf you add the following line to your mirage config file, it responds with a `500` error, which is a brutal internal server error code. \n\n```js\n// /app/mirage/config.js\nthis.post('categories', 'category', 500);\n```\n\nIf you extend your `app/routes/admin/categories.js` Category route handler with the following code, you can write out in your console the error message from mirage.\n\n```js\n    addNewCategory(name) {\n      this.store.createRecord('category', { name }).save().then(\n        category =\u003e {\n          console.info('Response:', category);\n          this.controller.set('newCategoryName', '');\n        },\n        error =\u003e {\n          console.error('Error from server:', error);\n        });\n    },\n```\n\nWe can improve further our model to manage the positive and negative responses automatically.\n\nBetter practice, if we create an empty model in the store when the user entering the page. On our category list page, our main model is a list of categories, which arrived from the server, this list is automatically added to the controller's `model` property.\n \n We can use the `setupController` hook in the route handler, to create a new empty category also and we can manually add it to a property, what we name it as `newCategory`.\n\n```js\n// app/routes/admin/categories.js\n  setupController(controller, model) {\n    this._super(controller, model);\n\n    controller.set('newCategory', this.store.createRecord('category'));\n  },\n```\n\nNow we can update our template:\n\n```handlebars\n\u003c!-- /app/templates/admin/categories.hbs --\u003e\n  \u003cdiv class=\"well well-sm\"\u003e\n    \u003cform class=\"form-inline\" {{action 'addNewCategory' newCategory on='submit'}}\u003e\n      \u003cdiv class=\"form-group\"\u003e\n        \u003clabel for=\"new-category\"\u003eNew category:\u003c/label\u003e\n        {{input type=\"text\" class=\"form-control\" id=\"new-category\" placeholder=\"Category name\" value=newCategory.name}}\n      \u003c/div\u003e\n      \u003cbutton type=\"submit\" class=\"btn btn-default\"\u003eAdd\u003c/button\u003e\n    \u003c/form\u003e\n  \u003c/div\u003e\n```\nAnd the action in route handler:\n\n```js\n    addNewCategory(newCategory) {\n      newCategory.save().then(\n        category =\u003e {\n          console.info('Response:', category);\n          this.controller.set('newCategory', this.store.createRecord('category'));\n        },\n        error =\u003e {\n          console.error('Error from server:', error);\n        });\n    },\n```\n\n### Error and loading state\n\nUsing `isError` to show some error message on the page.\n\n```handlebars\n\u003c!-- /app/templates/admin/categories.hbs --\u003e\n{{#if newCategory.isError}}\n  Error!!\n  {{#each newCategory.errors as |error|}}\n    {{error}}\n  {{/each}}\n{{/if}}\n```\nFurther options managing errors: `error.hbs` or `error` action. You can have an `error.hbs` in the main route our subroutes, Ember automatically will show that page if the server response with error. Other option is an `error` action in your route, if a request in `model()` hook is failed, this action will be called automatically. More details in the official guide: https://guides.emberjs.com/v2.9.0/routing/loading-and-error-substates/\n\nThere is a loading state also, you can show a loading spinner or a message while Ember is downloading data in `model()` hook. Drop a `loading.hbs` in your template folder and/or subfolders. Emulate a slow server with mirage. Uncomment this line in `app/mirage/config.js`: `this.timing = 400;` and rewrite 400 for 2000 (2 seconds).\n\n### Filter out the empty record\n\nPreviously we modified our route handler and we added a `createRecord()` in `setupController()` hook. Actually, this created a new empty record, which appears in the list. However, Ember Data automatically add a few state helper to the records. We will use `isNew` to filter out this record from the list.\n\nUpdate the template:\n\n```handlebars\n  {{#each model as |category|}}\n    {{#unless category.isNew}}\n      \u003ctr\u003e\n        \u003ctd\u003e{{category.id}}\u003c/td\u003e\n        \u003ctd\u003e\n          {{category.name}}\n        \u003c/td\u003e\n        \u003ctd\u003e\n          \u003cbutton class=\"btn btn-xs btn-danger\" {{action 'deleteCategory' category}}\u003eDel\u003c/button\u003e\n        \u003c/td\u003e\n      \u003c/tr\u003e\n    {{/unless}}\n  {{/each}}\n```\n\n### Edit a record\n\nExtend the category model:\n\n```js\n// app/models/category.js\nimport DS from 'ember-data';\n\nexport default DS.Model.extend({\n\n  name: DS.attr('string'),\n\n  isEditing: false\n\n});\n```\n\nEdit the name of a category with clicking on the name or a dedicated button.\n\n```handlebars\n  {{#each model as |category|}}\n    {{#unless category.isNew}}\n      \u003ctr\u003e\n        \u003ctd\u003e{{category.id}}\u003c/td\u003e\n        \u003ctd {{action 'editCategory' category}}\u003e\n          {{#if category.isEditing}}\n            {{input value=category.name}}\n            \u003cbutton {{action 'updateCategory' category}}\u003eSave\u003c/button\u003e\n          {{else}}\n            {{category.name}}\n          {{/if}}\n        \u003c/td\u003e\n        \u003ctd\u003e\n          \u003cbutton class=\"btn btn-xs btn-danger\" {{action 'deleteCategory' category}}\u003eDel\u003c/button\u003e\n          \u003cbutton class=\"btn btn-xs btn-success\" {{action 'editCategory' category}}\u003eEdit\u003c/button\u003e\n        \u003c/td\u003e\n      \u003c/tr\u003e\n    {{/unless}}\n  {{/each}}\n```\n\nAdd actions to the route handler:\n\n```js\nactions: {\n\n    addNewCategory(newCategory) {\n      newCategory.save().then(\n        category =\u003e {\n          console.info('Response:', category);\n          this.controller.set('newCategory', this.store.createRecord('category'));\n        },\n        error =\u003e {\n          console.error('Error from server:', error);\n        });\n    },\n\n    editCategory(category) {\n      category.set('isEditing', true);\n    },\n\n    updateCategory(category) {\n      category.save().then(\n        category =\u003e category.set('isEditing', false)\n      );\n    },\n\n    deleteCategory(category) {\n      category.destroyRecord();\n    }\n}\n```\n\nThe actual state of the categories admin page:\n\n![The categories admin page][step_3_1]\n\n## \u003ca name='homework-1'\u003e\u003c/a\u003eHomework 1 - Create the Admin page for Products\n\nCreate the Admin page for Products. You should basically repeat almost the same steps what we followed while we have been building the Categories page. \n\n1. Generate `admin/products` route and update the navigation bar on admin pages.\n\n2. Generate a `product` model with the following fields:\n  - `name` (string)\n  - `sku` (string) (Sku = stock keeping unit - usually this is the barcode number in a shop.)\n  - `unitPrice` (number)\n\n3. Mock product model and server calls with Mirage. (Use `ember generate mirage-model` and `ember generate mirage-factory`. Update the scenario and the config file in `mirage` folder. Check the Faker.js website and find a related method to generate random product names. Mirage should generate at least 20 products.)\n\n4. List all the products on `admin/products` page. (You have to add code to your `admin/product` route handler and implement handlebar in the connected template.)\n\n5. Add a form to the product list page, where you can create and save a new product, implement the connected actions.\n\n6. Add editing feature to the list. Three columns are in this list (name, sku, price). It is a nice solution, if there is an Edit button at the end of the row and clicking on this button, the row became a form. When the row is in editing state buttons should be \"Save\" and \"Cancel\". Implement the connected actions also.\n\n![A product in edit mode][step_3_2]\n\n[A possible solution in this commit][homework_1_solution_commit_link]\n\n## \u003ca name='relationship'\u003e\u003c/a\u003eLesson 3 - Manage relationship with Ember Data\n\nRelated guide: [Ember.js Guide - Model Relationships][ember_guide_relationships]\n\n**Requirements in this lesson:**\n\n- [ ] Extend Ember.js models with `hasMany` and `belongsTo` references.\n- [ ] Add the relationship to the mock, so Mirage can manage \n- [ ] Add a new option to Product form\n\n**Steps:**\n\n* Extend models.\n* Add extra column to Product list.\n* Update mirage models with associations\n  - http://www.ember-cli-mirage.com/docs/v0.2.x/models/#associations\n  - http://www.ember-cli-mirage.com/docs/v0.2.x/factories/#factories-and-relationships\n* Update mirage factories with `afterCreate()` \n* Add extra column to Categories with `{{category.products.length}}` \n* Check server calls in console, too many, reduce it with adding `includes` option to `findAll` in `model` hook.\n* Add a select box to the Product create form. (Using `emberx-select` addon.)\n\n```bash\n$ ember install emberx-select\n```\n\nhttps://github.com/thefrontside/emberx-select\n\n```handlebars\n    \u003cdiv class=\"form-group\"\u003e\n      \u003clabel for=\"category\"\u003eCategory:\u003c/label\u003e\n      {{#x-select id=\"category\" class=\"form-control\" value=newProduct.category as |xs|}}\n        {{#each categories as |category|}}\n          {{#xs.option value=category}}\n            {{category.name}}\n          {{/xs.option}}\n        {{/each}}\n      {{/x-select}}\n    \u003c/div\u003e\n```\n\n## \u003ca name='homework-2'\u003e\u003c/a\u003eHomework 2: List products on the home page.\n\n![List products on the homepage][homework_2]\n\n- [ ] Generate an `index` route.\n- [ ] Download all product in `model` hook.\n- [ ] List on `index.hbs` so it will appear on the home page.\n- [ ] Add some style, for example `panel` from Bootstrap.\n\n[A possible solution in this commit][homework_2_solution_commit_link]\n\n## \u003ca name='filter'\u003e\u003c/a\u003eLesson 4 - Filter products with categories\n\nWIP\n\n## \u003ca name='shopping-cart'\u003e\u003c/a\u003eLesson 5 - Creating a shopping cart service and add/remove products in shopping cart.\n\n(draft)\n\n**Reading:**\n\n* [Services][official_guide_services]\n* [Dependency Injection][official_guide_di]\n\n**Implementation steps:**\n\n- [ ] Create shopping cart service with add/remove functions.\n- [ ] Inject in the application (routes, controllers, components), so it will be available everywhere.\n- [ ] Implement an `Add to Cart` button and action in the product boxes on the home page. \n- [ ] Create a shopping-cart component, which will be available from everywhere, so we can remove items or finalize the order.\n \n```bash\n$ ember generate service shopping-cart\n```\n\n* Add button to the product panel\n* Add action to the `index.js` \n* Inject shopping-cart service\n\n* Add a badge to the navbar, but how we access to the `shoppingCart`\n\n* Dependency Injection, initializer\n\n```bash\n$ ember generate initializer shopping-cart\n```\n\n* Modal Window implementation\n* Named outlet\n* Checkout template and controller\n\n```shell\n$ ember g template checkout\n$ ember g controller checkout\n```\n\n* Render in application route\n* Close modal window action\n\n* List products from service\n* Remove item from the service\n* Passing the index instead of the product\n\n\n[ember_guide]: https://guides.emberjs.com/v3.1.0/getting-started/core-concepts\n[ember_cli_mock_server]: https://ember-cli.com/user-guide/#mocks-and-fixtures\n[actions_official_guide]: https://guides.emberjs.com/v3.1.0/templates/actions/\n[official_guide_models]: https://guides.emberjs.com/v3.1.0/models/\n[official_guide_services]: https://guides.emberjs.com/v3.1.0/applications/services/\n[official_guide_di]: https://guides.emberjs.com/v3.1.0/applications/dependency-injection/\n\n[route_handler_api]: http://emberjs.com/api/classes/Ember.Route.html\n[native_array_doc]: http://emberjs.com/api/classes/Ember.NativeArray.html\n\n[yoember.com]: http://yoember.com\n[ember_mirage]: http://www.ember-cli-mirage.com/\n[faker_js]: https://github.com/marak/Faker.js/\n[ember_bootstrap]: http://kaliber5.github.io/ember-bootstrap/\n[ember-cli-bootstrap-sassy]: https://github.com/lifegadget/ember-cli-bootstrap-sassy\n\n[homework_1_solution_commit_link]: https://github.com/zoltan-nz/product-app/commit/b52617e960401f0c1d0c749fc78ae96866b4a0e8\n\n[homework_2_solution_commit_link]: https://github.com/zoltan-nz/product-app/commit/d3bd96182847716388f471a916132eaaaef73880\n\n[ember_guide_relationships]: https://guides.emberjs.com/v3.1.0/models/relationships/\n\n[ember_concept_image]: https://guides.emberjs.com/v3.1.0/images/ember-core-concepts/ember-core-concepts.png\n[ember_store_image]: https://guides.emberjs.com/v2..0/images/guides/models/finding-unloaded-record-step1-diagram.png\n\n[nested-template]: doc/nested-template-ember.png\n[step_1]: doc/step_1.png\n[step_2_1]: doc/step_2_1.png\n[step_2_2]: doc/step_2_2.png\n[step_2_3]: doc/step_2_3.png\n[step_3_1]: doc/step_3_1.png\n[step_3_2]: doc/step_3_2.png\n[homework_2]: doc/homework_2.png\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzoltan-nz%2Fproduct-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzoltan-nz%2Fproduct-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzoltan-nz%2Fproduct-app/lists"}