<div ng-app="proto" ng-cloak>
  <div ng-controller="ListController as ctrl" layout="column" id="container">
    <section layout="row" flex>
      <rk-main-menu></rk-main-menu>
      <md-content flex>
        <md-toolbar md-scroll-shrink>
          <div class="md-toolbar-tools">
            <md-button class="md-icon-button" aria-label="Settings" ng-click="ctrl.openMainMenu()">
              <md-icon md-font-icon="fa-bars" class="fa" alt="Settings"></md-icon>
            </md-button>
            <h3><b>Lists</b></h3>
            <span flex></span>
            <md-button class="md-raised" aria-label="Add List" ng-click="ctrl.addList()">
              <md-icon md-font-icon="fa-plus" class="fa" alt="Add List"></md-icon>&nbsp;&nbsp;List
            </md-button>
            <md-button class="md-icon-button" aria-label="More" ng-click="ctrl.refreshAll()">
              <md-icon md-font-icon="fa-refresh" class="fa" alt="More"></md-icon>
            </md-button>
          </div>
        </md-toolbar>
        <md-content>
          <section>
            <md-subheader class="md-primary md-no-sticky">

              <table class="full-width" ng-if="ctrl.addingList"><tr><td>
                <md-button class="md-icon-button" ng-click="ctrl.finishListAdd()">
                  <md-icon md-font-icon="fa-check" class="fa" alt="Save"></md-icon>
                </md-button>
              </td><td class="full-width">
                <md-input-container class="full-width rk-no-padded">
                  <input type="text" ng-model="ctrl.listName" rk-enter="ctrl.finishListAdd()">
                </md-input-container>
              </td><td>
                <md-button class="md-icon-button" ng-click="ctrl.cancelListAdd()">
                  <md-icon md-font-icon="fa-ban" class="fa" alt="Cancel"></md-icon>
                </md-button>
              </td></tr></table>

              <table class="full-width" ng-if="ctrl.renamingList"><tr><td>
                <md-button class="md-icon-button" ng-click="ctrl.finishListRename()">
                  <md-icon md-font-icon="fa-check" class="fa" alt="Rename"></md-icon>
                </md-button>
              </td><td class="full-width">
                <md-input-container class="full-width rk-no-padded">
                  <input type="text" ng-model="ctrl.list.name" rk-enter="ctrl.finishListRename()">
                </md-input-container>
              </td><td>
                <md-button class="md-icon-button" ng-click="ctrl.cancelListRename()">
                  <md-icon md-font-icon="fa-ban" class="fa" alt="Cancel"></md-icon>
                </md-button>
              </td></tr></table>

              <table class="full-width" ng-if="!ctrl.addingList && !ctrl.renamingList"><tr><td class="full-width">
                <md-menu md-position-mode="target-left target">
                  <md-button class="full-width align-left" aria-label="Lists" ng-click="$mdOpenMenu($event)" style="margin: 0">
                    <md-icon md-font-icon="fa-sort-desc" class="fa float-right" alt="Lists"></md-icon>
                    {{ ctrl.getDropdownLabel() }}
                  </md-button>
                  <md-menu-content width="6">
                    <md-menu-item ng-repeat="s in ctrl.lists">
                      <md-button ng-click="ctrl.list = s">{{ s.name }}</md-button>
                    </md-menu-item>
                  </md-menu-content>
                </md-menu>
              </td><td ng-show="ctrl.list">
                <rk-action-menu icon="fa-bolt" actions="ctrl.listActions" context="ctrl" argument="ctrl.list"></rk-action-menu>
              </td></tr></table>
            </md-subheader>
            <div ng-if="ctrl.list">
              <rk-item-editor list="ctrl.list"></rk-item-editor>
              <md-list>
                <rk-item ng-repeat="list_item in ctrl.list.items | models track by list_item.$$key" list-item="list_item"></rk-item>
              </md-list>
            </div>
            <br><br>
          </section>
        </md-content>
      </md-content>
    </section>
  </div>
</div>

<script type="text/template" id="rkItemEditorTemplate">
  <div layout="row" layout-padding>
    <md-autocomplete flex="70" class="md-block rk-no-padded"
      md-no-cache="true"
      md-selected-item="editorCtrl.itemSelected"
      md-search-text="editorCtrl.itemName"
      md-items="item in editorCtrl.querySearch(editorCtrl.itemName)"
      md-item-text="item.name"
      md-select-on-match="true"
      md-match-case-insensitive="true"
      md-min-length="1"
      md-floating-label="Item (press enter to add)"
      rk-enter="editorCtrl.addItem()">
      <md-item-template>
        <span md-highlight-text="editorCtrl.itemName" md-highlight-flags="^i">{{ item.name }}</span>
      </md-item-template>
      <md-not-found>
        No existing items found.<br>A new item will be created.
      </md-not-found>
    </md-autocomplete>
    <md-input-container flex="30" class="md-block rk-no-padded" style="margin-top: 2px">
      <label style="left: 6px; bottom: 85%;">Quantity</label>
      <input type="text" ng-model="editorCtrl.itemAmount" rk-enter="editorCtrl.addItem()">
    </md-input-container>
  </div>
</script>

<script type="text/template" id="rkItemTemplate">
  <md-list-item class="rk-hover-hide md-2-line" ng-init="itemCtrl.sync()" rk-remote-update="itemCtrl.gottenAnimate">
    <md-checkbox
      ng-model="listItem.gotten"
      ng-change="itemCtrl.updateItem()"
      aria-label="Toggle Gotten"></md-checkbox>
    <div class="md-list-item-text" ng-init="item = listItem.item; pref = item.preference">
      <h3><span rk-remote-update="itemCtrl.nameAnimate">{{ item.name }}<span> - <b rk-remote-update="itemCtrl.amountAnimate">{{ listItem.amount }}</b></h3>
      <p>
        <span ng-show="pref.location" class="item-tag" rk-remote-update="itemCtrl.locationAnimate">
          <md-icon md-font-icon="fa-map-marker" class="fa"></md-icon>
          {{ pref.location }}
        </span>
        <span ng-show="pref.brand" class="item-tag" rk-remote-update="itemCtrl.brandAnimate">
          <md-icon md-font-icon="fa-registered" class="fa"></md-icon>
          {{ pref.brand }}
        </span>
        <span ng-show="pref.model" class="item-tag" rk-remote-update="itemCtrl.modelAnimate">
          <md-icon md-font-icon="fa-hashtag" class="fa"></md-icon>
          {{ pref.model }}
        </span>
      </p>
    </div>
    <rk-action-menu class="md-secondary rk-hover-hide-item" icon="fa-ellipsis-v" actions="itemCtrl.itemActions" context="itemCtrl" argument="listItem"></rk-action-menu>
  </md-list-item>
</script>

<script type="text/template" id="rkActionMenuTemplate">
  <md-menu md-position-mode="target-right target">
    <md-button class="md-icon-button float-right" aria-label="Actions" ng-click="$mdOpenMenu($event)">
      <md-icon md-font-icon="{{ icon }}" class="fa" alt="Actions"></md-icon>
    </md-button>
    <md-menu-content width="6">
      <md-menu-item ng-repeat="actionItem in actions track by actionItem.label">
        <md-button ng-click="actionMenu.performAction(this, actionItem, $event)">
          <div layout="row" flex>
            <md-icon md-menu-align-target md-font-icon="{{ actionItem.icon }}" class="fa" alt="{{ actionItem.label }}"></md-icon>
            <p flex>{{ actionItem.label }}</p>
          </div>
        </md-button>
      </md-menu-item>
    </md-menu-content>
  </md-menu>
</script>
    
<script type="text/template" id="rkConversionTemplate">
  <md-dialog aria-label="Conversions" ng-cloak>
    <form>
      <md-toolbar>
        <div class="md-toolbar-tools">
          <h2>Conversions</h2>
          <span flex></span>
          <md-button class="md-icon-button" ng-click="cancel()">
            <md-icon md-font-icon="fa-times" class="fa" aria-label="Close"></md-icon>
          </md-button>
        </div>
      </md-toolbar>
      <md-dialog-content style="padding: 30px">
        Conversions for <b>{{ parsed.normal || parsed }}</b>
        
        <div ng-if="!parsed.conversions">
          <p>
            This amount does not have a unit understood by this application. No conversions available!
          </p>
        </div>
        <div ng-if="parsed.conversions">
          <md-list>
            <md-list-item ng-repeat="conv in parsed.conversions" class="secondary-button-padding">
              <p>{{ conv.longNormal }}
                <span ng-show="conv.fraction.valid && conv.fraction.denominator !== 1">
                  ({{ conv.decimal.toFixed(2) }})
                </span>
              </p>
              <md-button class="md-secondary" ng-click="saveAmount(conv)">Use</md-button>
            </md-list-item>
          </md-list>
        </div>
        <p>
          Conversions available for {{ available }}.
        </p>
      </md-dialog-content>
      <md-dialog-actions layout="row">
        <span flex></span>
        <md-button ng-click="close()"style="margin-right:20px;">
          OK
        </md-button>
      </md-dialog-actions>
    </form>
  </md-dialog>
</script>

<script type="text/template" id="rkItemDialogTemplate">
  <md-dialog aria-label="Item Editor" ng-cloak>
    <form>
      <md-toolbar>
        <div class="md-toolbar-tools">
          <h2>Item Editor</h2>
          <span flex></span>
          <md-button class="md-icon-button" ng-click="cancel()">
            <md-icon md-font-icon="fa-times" class="fa" aria-label="Close"></md-icon>
          </md-button>
        </div>
      </md-toolbar>
      <md-dialog-content style="padding: 30px">
        <md-checkbox ng-model="list_item.gotten" class="md-block">
          Gotten
        </md-checkbox>
        <md-input-container class="md-block rk-light-highlight">
          <label>Item</label>
          <input type="text" ng-model="list_item.item.name" rk-enter="answer()">
        </md-input-container>
        <md-input-container class="md-block rk-light-highlight">
          <label>Quantity</label>
          <input type="text" ng-model="list_item.amount" rk-enter="answer()" md-autofocus>
        </md-input-container>
        <h4>Preferences</h4>
        <md-input-container class="md-block rk-light-highlight">
          <label>Brand</label>
          <input type="text" ng-model="list_item.item.preference.brand" rk-enter="answer()">
        </md-input-container>
        <md-input-container class="md-block rk-light-highlight">
          <label>Model</label>
          <input type="text" ng-model="list_item.item.preference.model" rk-enter="answer()">
        </md-input-container>
        <md-input-container class="md-block rk-light-highlight">
          <label>Location</label>
          <input type="text" ng-model="list_item.item.preference.location" rk-enter="answer()">
        </md-input-container>
      </md-dialog-content>
      <md-dialog-actions layout="row">
        <span flex></span>
        <md-button ng-click="cancel()"style="margin-right:20px;">
          Cancel
        </md-button>
        <md-button ng-click="answer()" style="margin-right:20px;">
          Save
        </md-button>
      </md-dialog-actions>
    </form>
  </md-dialog>
</script>

<script type="text/template" id="rkMainMenuTemplate">
  <md-sidenav class="md-sidenav-left md-whiteframe-z2" md-component-id="left" style="width:250px">
    <md-toolbar class="md-theme-indigo">
      <h1 class="md-toolbar-tools">
        <b>Your App</b>
        <span flex></span>
        <md-button class="md-icon-button" aria-label="Close Menu" ng-click="menuCtrl.close()">
          <md-icon md-font-icon="fa-times" class="fa" alt="Close Menu"></md-icon>
        </md-button>
      </h1>
    </md-toolbar>
    <md-content>
      <md-list>
        <md-list-item ng-repeat="menuItem in menuCtrl.menu track by menuItem.label" ng-click="menuCtrl.gotoMenu(menuItem, $event)">
          <md-icon md-font-icon="{{ menuItem.icon }}" class="fa" alt="{{ menuItem.label }}"></md-icon>
          <p> {{ menuItem.label }} </p>
        </md-list-item>
      </md-list>
    </md-content>
  </md-sidenav>
</script>
/* Animations */
.rk-remote-update-animation {
  -webkit-transition:0.5s linear all;
  transition:0.5s linear all;
}
.flash-pink {
  color: rgba(255,64,129,0.87);
}
.flash-blue {
  color: rgb(63,81,181);
}
.flash-background-blue {
  background-color: rgb(63,81,181) !important;
  color: white !important;
}
.flash-background {
  background-color: rgba(0,0,0,0.1);
}
/* Other */
.fa {
  font-size: 1.4em;
}
[ng-app] {
  height: 100%;
}
.rk-no-padded,
.rk-no-padded md-input-container {
  margin: 0px;
}
.md-block.rk-padded {
  margin: 18px 20px 0px 20px;
}
.rk-light-highlight {
  background-color: #f9f9f9;
}
._md-secondary-container,
._md-list-item-inner {
  height: auto !important;
}
._md-subheader-inner {
  padding: 4px !important;
}
md-list-item ._md-list-item-inner>md-checkbox, 
md-list-item>md-checkbox {
  margin-right: 16px;
}
md-list-item.md-2-line, md-list-item.md-2-line>._md-no-style {
  min-height: 52px;
}
#container {
  height: 100%;
}
.full-width {
  width: 100%;
}
.float-right {
  float: right;
}
.align-left {
  text-align: left;
}
.padded-top {
  padding-top: 16px;
}
.rk-hover-hide .rk-hover-hide-item {
  opacity: 0.1;
}
.rk-hover-hide:hover .rk-hover-hide-item {
  opacity: 1.0;
}
@media(max-width:600px) {
  .rk-hover-hide .rk-hover-hide-item {
    opacity: 1.0;
  }
}
.md-errors-spacer:empty {
  height: 0 !important;
  min-height: 0 !important;
}
.md-button.md-small-button {
  padding: 0;
  margin: 0;
}
.item-tag {
  font-size: 12px;
  color: #333;
  background-color: #eee;
  border-radius: 6px;
  padding: 2px 8px;
  margin: 0px;
  margin-right: 4px;
}
.item-tag .fa {
  font-size: 11px;
}
.md-scroll-mask {
  display: none;
}
// Define the model classes and relationships
function InitializeRekord()
{
  var ItemList = Rekord({
    name: 'item_list',
    fields: ['name'],
    comparator: 'name',
    hasMany: {
      items: {
        model: 'item_list_item',
        cascadeRemove: Rekord.Cascade.All // when the list is removed - delete the items
      }
    }
  });

  var ItemPreference = Rekord({
    name: 'item_preference',
    key: 'item_id',
    fields: ['brand', 'model', 'location'],
    belongsTo: {
      item: {
        model: 'item'
      }
    }
  });

  var Item = Rekord({
    name: 'item',
    fields: ['name'],
    defaults: {
      preference: ItemPreference,
    },
    hasOne: {
      preference: {
        model: 'item_preference',
        local: 'id',
        auto: false
      }
    }
  });
  
  var ItemListItem = Rekord({
    name: 'item_list_item',
    key: ['item_id', 'item_list_id'],
    fields: ['amount', 'gotten'],
    defaults: {
      gotten: false
    },
    belongsTo: {
      item: {
        model: 'item'
      },
      list: {
        model: 'item_list'
      }
    }
  });

  // 1. Loads local data
  // 2. Initializes relationships
  // 3. Resume pending operations if app was closed while offline
  // 4. Load remote data (based on load option)
  Rekord.load();
}

// Configure Defaults for this application
function ConfigureRekord()
{
  // Load all records from Firebase
  Rekord.Defaults.load = Rekord.Load.All;
  // Firebase has a complete set of data - if we request all records and we have a saved record locally - remove it
  Rekord.Defaults.allComplete = true;
  // Firebase handles saving locally - no need to interact with local storage
  Rekord.Defaults.cache = Rekord.Cache.None;
  // Firebase uses / to mean something, change it to something else.
  Rekord.Defaults.keySeparator = '--';
  // Automatically set the firebase API reference based on the name
  Rekord.Defaults.prepare = function(db, opts) {
    db.api = firebase.database().ref( db.name );
  };
}

// Initialize Firebase
firebase.initializeApp({
  apiKey: "AIzaSyA-Qwk3oifbsA2PG8YojFs91AKKCc91Kvw",
  authDomain: "rekord-firebase.firebaseapp.com",
  databaseURL: "https://rekord-firebase.firebaseio.com",
  storageBucket: "rekord-firebase.appspot.com"
});
firebase.auth().signInAnonymously().catch(function(error) {
  alert(error);
});

// Block certain models from being deleted for this example
var BLOCKED = [
  "0cf9bcb1-dba8-913e-798a-da622a34f087", /* Grocery List */
  "eea05ba0-6066-6909-b263-d56f40c5ed5e--0cf9bcb1-dba8-913e-798a-da622a34f087", // Ground Beef
  "415bf07f-fabb-4b76-7794-fd875da09ff1--0cf9bcb1-dba8-913e-798a-da622a34f087", // Sausage
  "701eab32-0291-25b3-04fd-4cace97cd240--0cf9bcb1-dba8-913e-798a-da622a34f087", // Eggs
  "97a0edd6-dfaa-d87a-9df4-d26677482ad2--0cf9bcb1-dba8-913e-798a-da622a34f087", // Bread
  "a75528b3-9cf3-ed9c-ec35-3e24cc74a2f0--0cf9bcb1-dba8-913e-798a-da622a34f087", // Banana
  "c5c359e7-5e14-a1e4-bee1-15557a4053f3--0cf9bcb1-dba8-913e-798a-da622a34f087" // Apple
];
var isBlocked = function(model) {
  return Rekord.indexOf( BLOCKED, model.$$key ) !== false;
};

// Angular 
var app = angular.module('proto', ['ngMaterial', 'material.svgAssetsCache', 'rekord']);

app.run( ConfigureRekord );
app.run( InitializeRekord );

app.factory( 'Item', Rekord.Factory.ref('item') );
app.factory( 'ItemPreference', Rekord.Factory.ref('item_preference') );
app.factory( 'ItemList', Rekord.Factory.ref('item_list') );
app.factory( 'ItemListItem', Rekord.Factory.ref('item_list_item') );

app.controller('ListController', function($scope, $mdSidenav, $mdMedia, $mdDialog, Item, ItemList, ItemPreference, ItemListItem, NaughtyWords) {
    
  var ctrl = this;
  
  // All changes to lists should update scope
  Rekord.Sync( $scope, ItemList );
  
  // Main Menu
  this.openMainMenu = function() {
    $mdSidenav('left').open();
  };
  
  // Example list is default
  var defaultList = null;

  // When lists are loaded - select the first one
  ItemList.ready(function(lists) {
    defaultList = ItemList.get( BLOCKED[0] );
    $scope.$evalAsync(function() {
      ctrl.list = ctrl.list || defaultList;
    });
  });
    
  // Refresh List
  this.refreshAll = function() {
    ItemList.refresh();
  };

  // List & Items
  this.list = null; // current list
  this.lists = ItemList.all();

  // Adding List
  this.addingList = false;
  this.listName = '';
  this.addList = function() {
    this.addingList = true;
    this.listName = '';
  };
  this.cancelListAdd = function() {
    this.addingList = false;
  };
  this.finishListAdd = function() {
    var name = this.listName.trim();
    if ( name ) {
      if ( NaughtyWords( name ) ) {
        alert('Try using more appropriate language!');
      } else {
        this.list = ItemList.create({name: name});
        this.listName = ''; 
      }
    }
    this.addingList = false;
  };

  // List Population
  this.getDropdownLabel = function() {
    if ( !this.list ) {
      return 'List ...';
    }
    var items = this.list.items;
    var name = this.list.name;

    return name + ' (' + items.countWhere('gotten', true) + '/' + items.length + ')';
  };

  // List Action Implementations
  this.reverse = function() {
    this.list.items.reverse();
  };
  this.removeGotten = function() {
    // this.list.items.removeWhere(true, 'gotten', true); // <- better version
    this.list.items.removeWhere(true /*call $remove()*/, function(list_item) {
      return !isBlocked(list_item) && list_item.gotten === true;
    });
    if (this.list.items.countWhere('gotten', true)) {
      alert('Some list items cannot be removed in this example list.');
    }
  };
  this.clearItems = function() {
    // this.list.items.unrelate(); // <- better version
    this.list.items.removeWhere(true /*call $remove()*/, function(list_item) {
      return !isBlocked(list_item);
    });
    if (this.list.items.length) {
      alert('Some list items cannot be removed in this example list.');
    }
  };
  this.getAll = function() {
    this.list.items.update('gotten', true);
  };
  this.dropGotten = function() {
    this.list.items.sort('-gotten');
  };
  this.orderItemsByLocation = function() {
    this.list.items.sort('item.preference.location');
  };
  this.removeList = function() {
    if (isBlocked(this.list)) {
      alert('This list can\'t be removed in this example. Try creating your own and removing it.');
    } else {
      this.list.$remove();
      this.list = defaultList;
    }
  };
  this.removeAll = function() {
    this.lists.each(function(list) {
       if (!isBlocked(list)) {
         list.$remove();
       } 
    });
    this.list = defaultList;
  };
  this.removeUnusedItems = function() {
    var preserveItems = {};
    ItemListItem.all().each(function(itemListItem) {
      if ( !itemListItem.list ) {
        itemListItem.$remove();
      } else {
        preserveItems[ itemListItem.item_id ] = true;
      }
    });
    Item.all().each(function(item) {
      if ( !preserveItems[ item.id ] ) {
        item.$remove();
      }
    });
    ItemPreference.all().each(function(pref) {
      if ( !pref.item || pref.item.$isDeleted() ) {
        pref.$remove();
      }
    });
  };

  // Renaming
  this.renamingList = false;
  this.cancelListRename = function() {
    this.list.$push();
    this.renamingList = false;
  };
  this.finishListRename = function() {
    this.list.$discard();
    this.list.$save();
    this.renamingList = false;
  };
  this.renameList = function() {
    if (isBlocked(this.list)) {
      alert('This list can\'t be renamed in this example. Try renaming or creating another list.');
    } else {
      this.list.$pop();
      this.renamingList = true;    
    }
  };
  this.refreshList = function() {
    this.list.$refresh();
  };

  // List Actions
  this.listActions = [
    {label: 'Refresh', icon: 'fa-refresh', action: this.refreshList}, // refresh
    {label: 'Rename', icon: 'fa-pencil', action: this.renameList}, // pencil
    {label: 'All gotten', icon: 'fa-check', action: this.getAll}, // ?
    {label: 'Move gotten to bottom', icon: 'fa-arrow-down', action: this.dropGotten}, // strike through
    {label: 'Reverse order', icon: 'fa-random', action: this.reverse}, // sort descending
    {label: 'Remove gotten from list', icon: 'fa-strikethrough', action: this.removeGotten}, // x
    {label: 'Order by location', icon: 'fa-map-marker', action: this.orderItemsByLocation}, // ?
    {label: 'Clear', icon: 'fa-eraser', action: this.clearItems}, // ?
    {label: 'Delete', icon: 'fa-trash', action: this.removeList}, // trash
    {label: 'Delete All (admin)', icon: 'fa-exclamation-triangle', action: this.removeAll}, // trash
    {label: 'Remove unused items (admin)', icon: 'fa-recycle', action: this.removeUnusedItems}
  ];

})
.directive('rkItemEditor', function() {
  return {
    replace: true,
    scope: {
      list: '='
    },
    template: rkItemEditorTemplate.innerHTML,
    controllerAs: 'editorCtrl',
    controller: function($scope, Item, ItemListItem, ItemPreference, NaughtyWords) {
      
      this.itemName = '';
      this.itemAmount = '';
      this.itemSelected = null;
      
      this.querySearch = function(text) {
        return Item.all().where('name', new RegExp(text, 'i'), Rekord.equals); // Rekord.equals must be specified to use regex
      };
      
      this.addItem = function() {
        var name = this.itemName.trim();
        var amount = this.itemAmount.trim();
        if (!name) {
          return;
        }
        if ( NaughtyWords( name ) ) {
          alert('Try using more appropriate language!');
          this.itemName = '';
          this.itemAmount = '';
          return;
        }
        var list = $scope.list;
        var item = this.itemSelected || Item.create({name: name});
        var listItem = ItemListItem.get({item: item, list: list});
        // Doesn't exist in the list or it's already gotten - use the given amount
        if ( !listItem || listItem.gotten ) {
          ItemListItem.persist({
            item: item,
            list: list,
            amount: amount,
            gotten: false
          });
        } 
        // The item exists in the list - add the amount
        else {
          listItem.amount = Unitz.combine( listItem.amount, amount );
          listItem.$save();
        }
        
        this.itemName = '';
        this.itemAmount = '';
        this.itemSelected = null;
      };
    }
  }
})
.directive('rkItem', function() {
  return {
    replace: true,
    scope: {
      listItem: '<'
    },
    template: rkItemTemplate.innerHTML,
    controllerAs: 'itemCtrl',
    controller: function($scope, $mdMedia, $mdDialog) {
      var list_item = $scope.listItem;
      var item = list_item.item;
      var preference = item.preference;
      
      // Animate when these properties remotely change
      this.amountAnimate = {model: list_item, field: 'amount', animation: 'flash-blue'};
      this.gottenAnimate = {model: list_item, field: 'gotten', animation: 'flash-background'};
      this.nameAnimate = {model: item, field: 'name', animation: 'flash-pink'};
      this.locationAnimate = {model: preference, field: 'location', animation: 'flash-background-blue'};
      this.brandAnimate = {model: preference, field: 'brand', animation: 'flash-background-blue'};
      this.modelAnimate = {model: preference, field: 'model', animation: 'flash-background-blue'};
      
      this.sync = function() {
        Rekord.Sync( $scope, list_item );
        Rekord.Sync( $scope, item );
        Rekord.Sync( $scope, preference );
      };
      this.updateItem = function() {
        list_item.$save();
      };
      this.removeItem = function() {
        if (isBlocked(list_item)) {
          alert('This list item can\'t be removed in this example.');
        } else {       
          list_item.$remove(); 
        }
      };
      this.editItem = function() {
        list_item.$push();
        item.$push();
        preference.$push();
      };
      this.cancelItem = function() {
        list_item.$pop();
      };
      this.finishItem = function() {
        list_item.$discard();
        item.$discard();
        preference.$discard();
        list_item.$save();
        item.$save();
        preference.$save();
      };
      this.showItemDialog = function(list_item, ev) {
        var ctrl = this;
        var promise = $mdDialog.show({
          controller: function($scope, $mdDialog) {
            ctrl.editItem();
            $scope.list_item = list_item;
            $scope.cancel = function() {
              $mdDialog.cancel();
            };
            $scope.answer = function() {
              $mdDialog.hide(list_item);
            };
          },
          template: rkItemDialogTemplate.innerHTML,
          parent: angular.element(document.body),
          targetEvent: ev,
          clickOutsideToClose: true,
          fullscreen: $mdMedia('sm') || $mdMedia('xs')
        });
        promise.then(function onAnswer(answer) {
          ctrl.finishItem();
        }, function onCancel() {
          ctrl.cancelItem();
        });;
      };
      this.showItemConversionDialog = function(list_item, ev) {
        var availableClasses = Rekord.collect( Unitz.classes ).pluck( 'className' );
        var availableLast = availableClasses.pop();
        var available = ( availableClasses.join( ', ' ) + ', and ' + availableLast ).toLowerCase();
        var promise = $mdDialog.show({
          controller: function($scope, $mdDialog) {
            $scope.parsed = Unitz.conversions(list_item.amount, false, 0.01, 1000);
            $scope.available = available;
            $scope.close = function() {
              $mdDialog.hide();
            };
            $scope.saveAmount = function(conv) {
              list_item.$save('amount', conv.decimal.toFixed(2) + ' ' + conv.shortUnit);
            };
          },
          template: rkConversionTemplate.innerHTML,
          parent: angular.element(document.body),
          targetEvent: ev,
          clickOutsideToClose: true,
          fullscreen: $mdMedia('sm') || $mdMedia('xs')
        });
      };     

      this.itemActions = [
        {label: 'Edit', icon: 'fa-pencil', action: this.showItemDialog},
        {label: 'Conversions', icon: 'fa-arrows-h', action: this.showItemConversionDialog},
        {label: 'Remove', icon: 'fa-times', action: this.removeItem}
      ];
    }
  }
})
.directive('rkRemoteUpdate', function($animate) {
  return function(scope, element, attrs) {
    var config = scope.$eval( attrs.rkRemoteUpdate ); // model, field, animation
    var handleChange = function(encoded, updated, previous, saved, conflicts) {
      if (config.field in updated && !Rekord.equals(updated[config.field], previous[config.field])) {
        $animate.addClass( element, config.animation ).then(function() {
          $animate.removeClass( element, config.animation );
        });
      }
    };
    var off = config.model.$on( Rekord.Model.Events.RemoteUpdate, handleChange );
    scope.$on( '$destroy', off );
    element.addClass('rk-remote-update-animation');
  }
})
.directive('rkMainMenu', function() {
  return {
    replace: true,
    template: rkMainMenuTemplate.innerHTML,
    controllerAs: 'menuCtrl',
    controller: function($mdSidenav) {
      // Main Menu
      this.menu = [
        {label: 'Lists', icon: 'fa-tasks'},
      ];
      this.close = function() {
        $mdSidenav('left').close();
      };
      this.open = function() {
        $mdSidenav('left').open();
      };
    }
  }
})
.directive('rkEnter', function() {
  return function(scope, element, attrs) {
    element.bind("keydown keypress", function (event) {
      if ( event.which == 13 ) {
        scope.$applyAsync( attrs.rkEnter );
        event.preventDefault();
      }
    });
  };
})
.directive('rkActionMenu', function() {
  return {
    scope: {
      icon: '@',
      actions: '<',
      context: '<',
      argument: '<',
      onAction: '&'
    },
    template: rkActionMenuTemplate.innerHTML,
    controllerAs: 'actionMenu',
    controller: function() {
      this.performAction = function($scope, actionItem, $event) {
        if ( actionItem.action ) {
          actionItem.action.call( actionItem.context || $scope.context, $scope.argument, $event, actionItem );
        } else {
          $scope.onAction({arg: $scope.argument, action: actionItem, $event: $event});
        }
      };
    }
  }
})
.factory('NaughtyWords', function() {
  var normalizers = [
    [/\s+/g, ''], [/\$/g, 's'], [/0/g, 'o'], [/1/g, 'i'],
    [/2/g, 'z'], [/3/g, 'e'], [/4/g, 'a'], [/5/g, 's'],
    [/6/g, 'b'], [/7/g, 'i'], [/8/g, 'b'], [/9/g, 'p'],
    [/!/g, 'l']
  ];
  var bad = /(fuck|piss|shit|nigg|dick|[^a-z]ass|cunt|damn|bitch|cock|pussy|fag|bastard|slut|douche)/i;
  return function hasNaughtyWords(words) {
    if (!Rekord.isString(words)) {
      return false;
    }
    for (var i = 0; i < normalizers.length; i++) {
      var n = normalizers[ i ];
      words = words.replace( n[0], n[1] );
    }
    return bad.test( words );
  };  
});
Run Pen

External CSS

  1. https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0-rc2/angular-material.min.css
  2. https://material.angularjs.org/1.0.8/docs.css
  3. https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css

External JavaScript

  1. https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.js
  2. https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-animate.min.js
  3. https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-aria.min.js
  4. https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-messages.min.js
  5. https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0-rc4/angular-material.min.js
  6. https://s3-us-west-2.amazonaws.com/s.cdpn.io/t-114/svg-assets-cache.js
  7. https://www.gstatic.com/firebasejs/3.0.2/firebase.js
  8. https://cdn.rawgit.com/Rekord/rekord/1827d1f16d55748db7ee1df896f3a95e9e675199/build/rekord.js
  9. https://cdn.rawgit.com/Rekord/rekord-firebase/5b5bbabfb2eb6d008b16098c5cb29a4f157079ed/build/rekord-firebase.min.js
  10. https://cdn.rawgit.com/Rekord/rekord-angular/39cb4631dca83d5fd6ff70ea25f9f2ae09612012/build/rekord-angular.min.js
  11. https://cdn.rawgit.com/ClickerMonkey/unitz/master/build/unitz.min.js