<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> 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 );
};
});