<!DOCTYPE html>
<html>

<head>
  <!-- Quick and free Bootstrap stylesheet CDN -->
  <link rel="stylesheet" href="//bootswatch.com/darkly/bootstrap.css">
  <style>
    /*Bootstrap angular ui cursor hotfix*/
    a,
    td {
      cursor: pointer;
    }
    
    th {
      font-size: 1.2em;
    }
    
    #addData {
      margin-bottom: 10px;
    }
  </style>
  <!-- Standard CDN Javascript Libraries -->
  <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.16/angular.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.13/angular-ui-router.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.0/ui-bootstrap.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.0/ui-bootstrap-tpls.js"></script>

  <!-- The library for which this example code is written -->
  <script src="anbomofo.min.js"></script>

  <!-- Bring all requied modal scripts -->
  <script src="myController.js"></script>
  <script src="myDataService.js"></script>

</head>

<body ng-app="app" ng-controller="MyController" class="container-fluid">

  <h1>Angular Data Driven Modals</h1>
  <p> Click on the "Add Data" button to launch the add data modal. Click on a table row to launch an edit data modal.</p>
  <p> Further details, source code, and a detailed explanation can be found <a href="https://github.com/mikemeding/Angular-Bootstrap-Modal-Forms" target="_blank"> here in my github repo.</a></p>

  <!-- This launches our add modal -->
  <button id="addData" class="btn btn-primary" modal ng-service="MyDataService">Add Data</button>

  <div class="panel panel-default">
    <div class="panel-heading">
      <h3>Data Table</h3></div>
    <div class="panel-body">

      <!--Table-->
      <table class="table table-hover table-condensed">
        <thead>
          <tr>
            <th>Customer Name</th>
            <th>Street Address</th>
            <th>City</th>
            <th>State</th>
            <th>Active</th>
          </tr>
        </thead>
        <tbody>

          <!-- The existance of ng-model here means that the modal is an edit modal using customer as its data-->
          <tr modal ng-service="MyDataService" ng-model="customer" ng-repeat="customer in customers">
            <td>{{customer.customerName}}</td>
            <td>{{customer.street}}</td>
            <td>{{customer.city}}</td>
            <td>{{customer.usState}}</td>
            <td>
              <input type="checkbox" ng-model="customer.active">
            </td>
          </tr>

        </tbody>
      </table>

    </div>
  </div>
</body>

</html>
(function() {
  'use strict';

  var app = angular.module('app');

  app.service('MyDataService', ['ModalService', function(ModalService) { // bring in ModalService as a dependency (contains compiler)

    // submit functions
    this.addOnSubmit = function(data) {
      console.log(data);
    };
    this.editOnSubmit = function(data) {
      console.log(data);
    };

    var model = {
      fields: [
      // making display false preserves the data but does not display it
      {
        name: "id",
        display: false
      },
      
      // what a checkbox boolean looks like
      {
        name: "active",
        type: "checkbox",
        displayName: "Active?"
      },
      {
        name: "customerName",
        displayName: "Customer Name",
        placeholder: "Please enter a persons name.",
        type: "text",
        required: true // constraints can be added as well
      },
      {
        name: "street",
        displayName: "Street",
        type: "text",
        placeholder: "123 Street Rd"
      },
      {
        name: "city",
        displayName: "City",
        type: "text",
        placeholder: "kalamazoo"
      },
      
      // a dropdown with many options example
      {
        name: "usState",
        displayName: "State",
        type: "dropdown",
        dropdownOptions: [
          "AL", "AK", "AS", "AZ", "AR", "CA", "CO", "CT", "DE", "DC", "FM", "FL", "GA", "GU", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MH", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY", "NC", "ND", "MP", "OH", "OK", "OR", "PW", "PA", "PR", "RI", "SC", "SD", "TN", "TX", "UT", "VT", "VI", "VA", "WA", "WV", "WI", "WY"
        ]
      },
      {
        name: "email",
        displayName: "Email",
        type: "text",
        placeholder: "example@gmail.com"
      },
      {
        name: "phone",
        displayName: "Phone",
        type: "text",
        placeholder: "(999)999-9999"
      }],
      
      // the callback functions are defined above
      addModalSettings: {
        title: 'Add New Person',
        callback: this.addOnSubmit
      },
      editModalSettings: {
        title: 'Edit Person',
        callback: this.editOnSubmit
      }

    };

    // you must compile and update your model
    model = ModalService.compileModel(model);

    // this method must be implemented to allow external access to your model
    this.getModel = function() {
      return model;
    };

  }]);
}());
(function() {
  'use strict';
  var app = angular.module('app', ['ui.bootstrap', 'autoModals', 'ui.router']);
  app.controller('MyController', ['MyDataService', '$scope', '$http', function(ModalService, $scope, $http) {

    $scope.customers = [];

    // fetch our data from the customers.json
    $scope.fetchCustomers = function() {
      var request = {
        method: 'GET',
        url: 'customers.json'
      };
      $http(request)
        .success(function(data, status, headers, config, response) {
          $scope.customers = data.list;
        })
        .error(function(data, status, headers, config, response) {
          console.error("Failed getting customers.json file");
        });
    };
    $scope.fetchCustomers(); // on page load

  }]);
}());
(function(){var a=angular.module("autoModals",[]);a.directive("modal",function(){return{restrict:"A",scope:{ngService:"@",ngModel:"="},controller:["$scope","$attrs","$injector","$modal",function(d,c,f,e){d.dataServiceName=c.ngService;d.dataService=f.get(d.dataServiceName);d.openModal=function(){function g(){e.open({template:'<div class="modal-header">     <div class="row">         <div class="col-lg-9">             <h3 class="modal-title">{{title}}</h3>         </div>         <div class="col-lg-3">             <h5 class="pull-right"><strong class="text-info">BLUE</strong> means required</h5>         </div>     </div> </div>  <div class="modal-body">       <form>         <!--Loop over all fields in the data model-->         <div class="form-group" ng-class="{\'has-error\' : !field.valid}" ng-repeat="field in fields"              ng-if="field.display">              <!--for boolean attributes-->             <input ng-if="field.type == \'checkbox\'" type="checkbox" ng-model="field.value">              <!--Field Label-->             <label ng-class="{\'text-info\' : field.required && field.valid, \'text-default\' : !field.required && field.valid , \'text-danger\': !field.valid}"                    for="{{field.name}}">{{field.displayName}} </label>              <!--Error message-->             <div ng-if="field.hasOwnProperty(\'errorMessage\')" class="alert alert-danger alert-dismissible fade in" role="alert">                 <strong>Error!</strong> {{field.errorMessage}}             </div>              <!--Create an input field for text and number attributes-->             <input ng-if="field.type == \'text\' || field.type == \'number\'" type="{{field.type}}" class="form-control"                    placeholder="{{field.placeholder}}" id="{{field.name}}" ng-model="field.value">              <!--Create a dropdown field for dropdown attributes using dropdownOptions attribute-->             <select class="form-control" ng-if="field.type == \'dropdown\'" data-ng-model="field.value" data-ng-options="item for item in field.dropdownOptions">             </select>              <!--for datetime picker-->             <input ng-if="field.type == \'datetime\'" class="form-control" type="datetime-local" ng-model="field.value">          </div>     </form> </div>  <!--modal footer--> <div class="modal-footer">     <button type="submit" class="btn btn-primary" ng-click="ok(fields)">OK</button>     <button type="button" class="btn btn-default" ng-click="cancel()">Cancel</button> </div>',controller:"AddModalInstanceController",size:"lg",resolve:{ngModel:function(){return d.ngModel},dataServiceName:function(){return d.dataServiceName}}})}if(typeof d.dataService.preClick==="function"){d.dataService.preClick()}function h(){setTimeout(function(){if(!d.ready){d.ready=d.dataService.modelReady();h()}else{g()}},100)}if(typeof d.dataService.modelReady==="function"){d.ready=d.dataService.modelReady();h()}else{g()}}}],link:function b(d,e,c,f){e.bind("click",function(g){d.openModal()})}}});a.controller("AddModalInstanceController",["$scope","$modalInstance","$http","$state","$injector","dataServiceName","ngModel","ModalService",function(k,f,g,b,j,i,d,c){var h=j.get(i);var e=h.getModel();k.fields=e.fields;if(d===undefined){k.settings=e.addModalSettings}else{k.settings=e.editModalSettings;k.fields=c.addValuesToFields(d,k.fields)}k.title=k.settings.title;k.cancel=function(){f.dismiss("cancel")};k.ok=function(l){var n=c.getValuesFromFields(l);var m=function(){k.fields=c.checkValidRequirements(k.fields);if(!c.checkValidFromFields(k.fields)){k.fields=l}else{if(k.settings.hasOwnProperty("callback")){k.settings.callback(n)}f.close()}};if("validate" in k.settings){k.fields=c.resetValidity(l);k.fields=k.settings.validate(k.fields);m()}else{k.fields=c.resetValidity(l);m()}};f.result.then(function(l){k.fields=c.resetModel(h.getModel())},function(l){k.fields=c.resetModel(h.getModel())})}]);a.service("ModalService",[function(){this.compileModel=function(d){var f=false;for(var c=0;c<d.fields.length;c++){var e=d.fields[c];if(e.type==="dropdown"&&!("dropdownOptions" in e)){console.error("MODAL ERROR: dropdown field ["+e[name]+"] must have dropdownOptions Array");f=true}if(!("name" in e)){console.error("MODAL ERROR: name field must exist in field ["+JSON.stringify(e)+"]");f=true}if(!("type" in e)&&e.display){console.error("MODAL ERROR: type field must exist in field ["+JSON.stringify(e)+"]");f=true}}if(f){console.error("Model compile error. Incorrectly formatted model.");return{}}else{return b(d)}};var b=function(d){var f={};for(var c=0;c<d.fields.length;c++){var e=d.fields[c];if(!("display" in e)){e.display=true}if(!("valid" in e)){e.valid=true}if(!("required" in e)){e.required=false}if(!("displayName" in e)){e.displayName=e.name}if(!("placeholder" in e)){e.placeholder="Please enter a value"}if(e.type==="dropdown"){if(!e.hasOwnProperty("value")){e.value=e.dropdownOptions[0]}}if(e.type==="checkbox"){if(!e.hasOwnProperty("value")){e.value=false}}if(e.type==="datetime"){if(!e.hasOwnProperty("value")){e.value=new Date()}}}return d};this.resetModel=function(d){for(var c=0;c<d.fields.length;c++){var e=d.fields[c];if(e.type==="dropdown"){e.value=e.dropdownOptions[0]}else{delete e.value}e.valid=true;delete e.errorMessage}return d};this.getValuesFromFields=function(d){var f={};for(var c=0;c<d.length;c++){var e=d[c];if("value" in e){f[e.name]=e.value}else{if(e.type==="checkbox"){f[e.name]=false}}}return f};this.checkValidFromFields=function(d){var e=true;for(var c=0;c<d.length;c++){var f=d[c];if(!f.valid){e=false}}return e};this.checkValidRequirements=function(d){for(var c=0;c<d.length;c++){var e=d[c];if("required" in e){if(e.required&&e.valid&&!("value" in e)){e.valid=false;e.errorMessage="This field is required"}}}return d};this.resetValidity=function(d){for(var c=0;c<d.length;c++){var e=d[c];e.valid=true;delete e.errorMessage}return d};this.addValuesToFields=function(e,d){for(var c=0;c<d.length;c++){var g=d[c];for(var f in e){if(e.hasOwnProperty(f)){if(g.name===f){g.value=e[f]}}}}return d}}])}());
{
  "status": "OK",
  "version": 1.0,
  "list": [{
    "id": 6,
    "customerName": "4 Energy",
    "active": true,
    "street": "6637 Front Street",
    "city": "North Anchorange",
    "usState": "AK",
    "email": "email@email.com",
    "phone": "555-555-5555"
  }, {
    "id": 9,
    "customerName": "Cara Donna",
    "active": false,
    "street": "7303 York Street",
    "city": "Winchester",
    "usState": "OH",
    "email": "email@email.com",
    "phone": "666-666-6666"
  }, {
    "id": 14,
    "customerName": "Colony Foods",
    "active": true,
    "street": "7303 York Street",
    "city": "Winchester",
    "usState": "OH",
    "email": "email@email.com",
    "phone": "666-666-6666"
  }, {
    "id": 4,
    "customerName": "Emerson",
    "active": false,
    "street": "6637 Front Street",
    "city": "North Anchorange",
    "usState": "AK",
    "email": "email@email.com",
    "phone": "555-555-5555"
  }, {
    "id": 8,
    "customerName": "Magniture",
    "active": true,
    "street": "7303 York Street",
    "city": "Winchester",
    "usState": "OH",
    "email": "email@email.com",
    "phone": "666-666-6666"
  }, {
    "id": 15,
    "customerName": "MBTA",
    "active": true,
    "street": "7303 York Street",
    "city": "Winchester",
    "usState": "OH",
    "email": "email@email.com",
    "phone": "666-666-6666"
  }, {
    "id": 10,
    "customerName": "Ice Cream",
    "active": false,
    "street": "6637 Front Street",
    "city": "North Anchorange",
    "usState": "AK",
    "email": "email@email.com",
    "phone": "555-555-5555"
  }, {
    "id": 11,
    "customerName": "Novartis",
    "active": true,
    "street": "7303 York Street",
    "city": "Winchester",
    "usState": "OH",
    "email": "email@email.com",
    "phone": "666-666-6666"
  }, {
    "id": 1,
    "customerName": "OutSmart Power Systems",
    "active": true,
    "street": "7303 York Street",
    "city": "Winchester",
    "usState": "OH",
    "email": "email@email.com",
    "phone": "666-666-6666"
  }, {
    "id": 7,
    "customerName": "Parker Hannifin",
    "active": true,
    "street": "6637 Front Street",
    "city": "North Anchorange",
    "usState": "AK",
    "email": "email@email.com",
    "phone": "555-555-5555"
  }, {
    "id": 5,
    "customerName": "Progress Software",
    "active": true,
    "street": "7303 York Street",
    "city": "Winchester",
    "usState": "OH",
    "email": "email@email.com",
    "phone": "666-666-6666"
  }, {
    "id": 13,
    "customerName": "Roche Brothers",
    "active": true,
    "street": "7303 York Street",
    "city": "Winchester",
    "usState": "OH",
    "email": "email@email.com",
    "phone": "666-666-6666"
  }, {
    "id": 2,
    "customerName": "Stop and Shop",
    "active": false,
    "street": "6637 Front Street",
    "city": "North Anchorange",
    "usState": "AK",
    "email": "email@email.com",
    "phone": "555-555-5555"
  }, {
    "id": 12,
    "customerName": "UTC",
    "active": true,
    "street": "7303 York Street",
    "city": "Winchester",
    "usState": "OH",
    "email": "email@email.com",
    "phone": "666-666-6666"
  }, {
    "id": 16,
    "customerName": "WCUPA",
    "active": true,
"street": "7303 York Street",
    "city": "Winchester",
    "usState": "OH",
    "email": "email@email.com",
    "phone": "666-666-6666"
  }]
}