In my previous post I created a simple [AngularJS – Breeze] edit view. In this post I will add the create, delete, reset and “is dirty” (entity change state tracking) to the simple view.
Before create
Create
The user clicked on the create button, so an “is dirty” icon is shown and a new row is added to the grid.
Save
When the user fills the fields of the new row and clicks save, the “is dirty” icon will disappear and the id field will automatically be filled by the id generated on the server in the database.
Reset
When the user create, updated and deleted some records and pressed save, the original state of the database can be restored by pressing the reset button.
index.html
<!DOCTYPE html> <html data-ng-app="app"> <head> <title data-ng-bind="title">Angular and Breeze</title> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> <!-- Libraries --> <link rel="stylesheet" type="text/css" href="../../../Libraries/FontAwesome/css/font-awesome.min.css" /> <link rel="stylesheet" type="text/css" href="../../../Libraries/Toastr/toastr.min.css" /> <!-- App --> <link rel="stylesheet" type="text/css" href="app.css" /> </head> <body> <div class="spa-page" data-ng-controller="admin as vm"> <div class="spa-grid-toolbar"> <a class="spa-action-link" ng-click="vm.save()">save</a> | <a class="spa-action-link" ng-click="vm.reset()">reset</a> | <a class="spa-action-link" ng-click="vm.create()">create</a> <i class="fa fa-exclamation-circle" title="Some data has change. Press save to save the changes to the server!" ng-show="vm.isDirty"></i> </div> <table class="spa-grid"> <thead> <tr> <th> </th> <th ng-repeat='(key, prop) in vm.entityFields'>{{ prop.name }}</th> </tr> </thead> <tbody> <tr ng-repeat="entity in vm.entities"> <td><a class="spa-action-link" ng-click="vm.delete(entity)">delete</a></td> <td ng-repeat='(key, prop) in vm.entityFields'> <input ng-disabled="{{vm.isReadOnlyField(prop.name)}}" type='text' ng-model='entity[prop.name]'> </td> </tr> </tbody> </table> </div> <!-- Libraries --> <script type="text/javascript" src="../../../Libraries/Angular/angular.js"></script> <script type="text/javascript" src="../../../Libraries/Breeze/breeze.debug.js"></script> <script type="text/javascript" src="../../../Libraries/Breeze/breeze.angular.js"></script> <!-- Add toastr which needs jQuery (Breeze does not need jQuery) --> <script type="text/javascript" src="../../../Libraries/jQuery/jquery-2.1.1.js"></script> <script type="text/javascript" src="../../../Libraries/Toastr/toastr.js"></script>
<!-- Add breeze.savequeuing which needs Q (Breeze does not need Q)--> <script type="text/javascript" src="../../../Libraries/Q/q.min.js"></script> <script type="text/javascript" src="../../../Libraries/Breeze/breeze.savequeuing.js"></script> <!-- App --> <script type="text/javascript" src="app.js"></script> </body> </html>
app.js
// Use namespaces to prevent pollution of the global namespace. var spa = spa || {}; spa.controllers = spa.controllers || {}; // Angular module [app]. spa.app = (function () { 'use strict'; var app = angular.module('app', [ 'breeze.angular' // The breeze service module. ]); })(); // Angular controller [admin]. spa.controllers.admin = (function () { 'use strict'; var controllerId = 'admin'; angular.module('app').controller(controllerId, ['$http', 'breeze', admin]); function admin($http, breeze) { var entityChangedToken = null; var entityTypeName = "Employee"; var manager = null; var vm = this; vm.create = function () { // Create entity by breeze. var entity = manager.createEntity(entityTypeName); // Show entity to user. vm.entities.push(entity); }; vm.delete = function (entity) { // Delete from UI vm.entities.pop(entity); // Mark for deletion. entity.entityAspect.setDeleted(); }; vm.entities = []; vm.entityFields = null; vm.isDirty = false; vm.isReadOnlyField = function (name) { // Make 'id' fields read-only. return (name === 'id'); }; vm.reset = function () { // Re-seed database and refetch data. $http.get('/breeze/breeze/ReSeed').then(getData).then(handleResetResult).catch(showError); }; vm.save = function () { manager.saveChanges().then(handleSaveResult).catch(showError); }; function handleStateChange(args) { vm.isDirty = true; } function getData() { // Get entities from the server. var query = new breeze.EntityQuery().from(entityTypeName); manager.executeQuery(query).then(handleGetDataResult).catch(showError); } function initialize() { // Use camel case for entity properties. breeze.NamingConvention.camelCase.setAsDefault(); // Configure and create EntityManager (double breeze is needed, because of . manager = new breeze.EntityManager('/breeze/breeze'); manager.enableSaveQueuing(true); registerForStateChange(); getData(); } function handleGetDataResult(data) { // Get entity fields from metadata. var entityMetaData = manager.metadataStore.getEntityType(entityTypeName); vm.entityFields = entityMetaData.dataProperties; // Show the enties from the server. vm.entities = data.results; vm.isDirty = false; } function handleResetResult() { vm.isDirty = false; toastr.info("Database re-seeded."); } function handleSaveResult() { vm.isDirty = false; toastr.info("Changes saved to the server."); } function registerForStateChange() { // Make sure to only subscribe once. if (entityChangedToken) { return; } // Register for state change. entityChangedToken = manager.entityChanged.subscribe(handleStateChange); } function showError(e) { // Show xhr error. toastr.error(e); } initialize(); } })();
app.css
/* Resets */ html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre,abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary, time, mark, audio, video { border: 0; /* Prevent unnecessary white space. */ -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; /* Border boxing is used, so the padding, margin and borders are within the width and height of the element. */ margin: 0; /* Prevent unnecessary white space. */ outline: 0; /* Prevent unnecessary white space. */ padding: 0; /* Prevent unnecessary white space. */ font-size: 100%; vertical-align: baseline; } html, body { height: 100%; /* Full screen single page app. */ max-height: 100%; /* Full screen single page app. */ } body { padding: 20px; } a { cursor: pointer; } a.spa-action-link { color: #428bca; text-decoration: none; } a.spa-action-link:hover, a.spa-action-link:focus { color: #2a6496; text-decoration: underline; } div.spa-page { border: 1px solid rgb(212, 212, 212); height: 100%; /* Full screen single page app. */ max-height: 100%; /* Full screen single page app. */ padding: 20px; position: relative; } div.spa-page > a { margin-bottom: 10px; } div.spa-grid-toolbar i.fa-exclamation-circle { margin-left: 10px; } .spa-grid input[type="text"] { padding-left: 2px; }
BreezeController.cs
namespace Research.UI.Web.Server.Controllers { using Breeze.ContextProvider; using Breeze.ContextProvider.EF6; using Breeze.WebApi2; using Newtonsoft.Json.Linq; using Research.UI.Web.Server.Model; using System.Data.Entity.Migrations; using System.Linq; using System.Web.Http; [BreezeController] public class BreezeController : ApiController { private readonly EFContextProvider<ResearchDbContext> _contextProvider; public BreezeController(): this(null) { } public BreezeController(EFContextProvider<ResearchDbContext> contextProvider) { _contextProvider = contextProvider ?? new EFContextProvider<ResearchDbContext>(); } [HttpGet] public IQueryable<Employee> Employee() { return _contextProvider.Context.Employees; } [HttpGet] public string Metadata() { string result = _contextProvider.Metadata(); return result; } [HttpGet] public void ReSeed() { // Remove all records from the "Employees" table. _contextProvider.Context.Database.ExecuteSqlCommand("truncate table Employees"); // Run an "Update-Database" EF migrations command, this will update the database schema
// to the latest state and run the Seed() method. var configuration = new Research.UI.Web.Migrations.Configuration(); configuration.ContextType = typeof(ResearchDbContext); var migrator = new DbMigrator(configuration); migrator.Update(); } [HttpPost] public SaveResult SaveChanges(JObject saveBundle) { return _contextProvider.SaveChanges(saveBundle); } } }
For the complete code, see: