Fix: Date filtering works properly in Tokenizer (requires moment.js)

Feature: Selecting Start/Finish Date in Recipe list updates the search string.
This commit is contained in:
tanshu 2016-03-16 14:41:58 +05:30
parent 3c4f24989c
commit 3df2146ba2
6 changed files with 134 additions and 69 deletions

View File

@ -1,10 +1,10 @@
<h2>Products</h2> <h2>Products</h2>
<div layout-gt-sm="row"> <div layout="row">
<md-input-container flex-gt-sm> <md-input-container flex="grow">
<input type="text" ng-model="search" <input type="text" ng-model="search" md-autofocus
placeholder="Search: n:Name, a:Active, u:Units, t:Type and e:true for extended properties"> placeholder="Search: n:Name, a:Active, u:Units, t:Type and e:true for extended properties">
</md-input-container> </md-input-container>
<md-input-container flex-gt-sm> <md-input-container flex>
<md-button ng-href="/Product">Add <i <md-button ng-href="/Product">Add <i
class="glyphicon glyphicon-plus"></i></md-button> class="glyphicon glyphicon-plus"></i></md-button>
</md-input-container> </md-input-container>

View File

@ -1,7 +1,7 @@
<form class="form-horizontal" role=form> <form class="form-horizontal" role=form>
<h1>Recipe Detail</h1> <h1>Recipe Detail</h1>
<div layout-gt-sm="row"> <div layout="row">
<md-autocomplete md-selected-item="recipe.Product" md-search-text="searchProd" <md-autocomplete md-selected-item="recipe.Product" md-search-text="searchProd"
md-autofocus="recipe.RecipeItems.length === 0" md-autofocus="recipe.RecipeItems.length === 0"
md-items="item in products(searchProd, {p: false})" md-item-text="item.Name" on-return="add()" md-items="item in products(searchProd, {p: false})" md-item-text="item.Name" on-return="add()"
@ -30,11 +30,11 @@
value="{{costPrice | currency}} / {{costPrice * 100 / recipe.SalePrice | number:2}}%"/> value="{{costPrice | currency}} / {{costPrice * 100 / recipe.SalePrice | number:2}}%"/>
</md-input-container> </md-input-container>
</div> </div>
<div layout-gt-sm="row"> <div layout="row" class="md-inline-form">
<md-datepicker md-placeholder="Valid From" ng-model="recipe.ValidFrom"></md-datepicker> <md-datepicker md-placeholder="Valid From" ng-model="recipe.ValidFrom"></md-datepicker>
<md-datepicker md-placeholder="Valid To" ng-model="recipe.ValidTo"></md-datepicker> <md-datepicker md-placeholder="Valid To" ng-model="recipe.ValidTo"></md-datepicker>
</div> </div>
<div layout-gt-sm="row"> <div layout="row">
<md-autocomplete md-selected-item="product" md-search-text="searchIngredient" focus-on="foIngredient" <md-autocomplete md-selected-item="product" md-search-text="searchIngredient" focus-on="foIngredient"
md-autofocus="recipe.RecipeItems.length > 0" md-items="item in products(searchIngredient, {})" md-autofocus="recipe.RecipeItems.length > 0" md-items="item in products(searchIngredient, {})"
md-item-text="item.Name" on-return="add()" md-item-text="item.Name" on-return="add()"

View File

@ -1,35 +1,44 @@
<form class="form-horizontal" role=form> <h1>Recipes</h1>
<h2>Recipes <div layout="row">
<div class="form-group col-md-9 pull-right"> <md-input-container flex="grow">
<div class="col-md-10"> <input type="text" ng-model="search" md-autofocus placeholder="Search: n:Name, s:Start Date, f:Finish Date">
<input type="text" class="form-control" placeholder="Search" ng-model="search"> </md-input-container>
</div> <md-input-container flex>
<div class="col-md-2"> <md-button ng-href="/Recipe">Add <i
<a href="/Recipe" class="btn btn-success btn-block">Add <i class="glyphicon glyphicon-plus"></i></md-button>
class="glyphicon glyphicon-plus"></i></a> </md-input-container>
</div> </div>
</div> <div layout="row">
</h2> <div layout="column" flex>
<table class="table table-condensed table-bordered table-striped"> <label class="md-caption">Start Date</label>
<thead> <md-datepicker md-placeholder="Start Date" ng-model="startDate"
<tr> ng-model-options="{ getterSetter: true }"></md-datepicker>
<th>Name</th> </div>
<th>Valid From</th> <div layout="column" flex>
<th>Valid To</th > <label class="md-caption">Finish Date</label>
<th class="text-right">Cost Price</th> <md-datepicker md-placeholder="Finish Date" ng-model="finishDate"
<th class="text-right">Sale Price</th> ng-model-options="{ getterSetter: true }"></md-datepicker>
<th class="text-right">Costing</th> </div>
</tr> </div>
</thead> <table class="table table-condensed table-bordered table-striped">
<tbody ng-repeat="items in info"> <thead>
<tr ng-repeat="item in items.Prices"> <tr>
<td ng-if="$first" rowspan="{{items.Prices.length}}">{{items.Name}}</td> <th>Name</th>
<td><a href="{{item.Url}}">{{item.ValidFrom}}</a></td> <th>Valid From</th>
<td>{{item.ValidTo}}</td> <th>Valid To</th>
<td class="text-right">{{item.CostPrice | currency}}</td> <th class="text-right">Cost Price</th>
<td class="text-right">{{item.SalePrice | currency}}</td> <th class="text-right">Sale Price</th>
<td class="text-right">{{item.Costing | percent}}</td> <th class="text-right">Costing</th>
</tr> </tr>
</tbody> </thead>
</table> <tbody ng-repeat="items in recipes">
</form> <tr ng-repeat="item in items.Prices">
<td ng-if="$first" rowspan="{{items.Prices.length}}">{{items.Name}}</td>
<td><a href="{{item.Url}}">{{item.ValidFrom}}</a></td>
<td>{{item.ValidTo}}</td>
<td class="text-right">{{item.CostPrice | currency}}</td>
<td class="text-right">{{item.SalePrice | currency}}</td>
<td class="text-right">{{item.Costing | percent}}</td>
</tr>
</tbody>
</table>

View File

@ -270,33 +270,17 @@ overlordService.factory('Tokenizer', ['$filter', function ($filter) {
} }
}, },
'date': function (obj, text) { 'date': function (obj, text) {
var reGoodDate = /^((0?[1-9]|[12][0-9]|3[01])[- /.](0?[1-9]|1[012]|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[- /.](19|20)?[0-9]{2})*$/i, var obDate = moment(obj, 'DD-MMM-YYYY'),
asDateObject = function (input) { op = operatorLength(text),
if (angular.isDate(input)) { operator = op ? text.substr(0, op) : '==',
return input; textDate = op ? moment(text.substr(op).trim(), 'DD-MMM-YYYY') : moment(text, 'DD-MMM-YYYY');
} return !obDate.isValid() || !textDate.isValid() || operators[operator](obDate.valueOf(), textDate.valueOf());
},
op = opLength(text),
ob = op ? asDateObject(obj) : '',
tex = op ? asDateObject(text.substr(op).trim()) : '';
if (op) {
return operators[text.substr(0, op)](ob, tex);
} else {
return obj.toString().indexOf(text) > -1;
}
if (text.indexOf('!') === 0) {
return obj.toLowerCase().indexOf(text.substr(1)) <= -1;
} else {
return obj.toLowerCase().indexOf(text) > -1;
}
}, },
'boolean': function (obj, text) { 'boolean': function (obj, text) {
return obj === isTrue(text); return obj === isTrue(text);
}, },
'numeric': function (obj, text) { 'numeric': function (obj, text) {
var op = opLength(text); var op = operatorLength(text);
if (op) { if (op) {
return operators[text.substr(0, op)](obj, Number(text.substr(op).trim())); return operators[text.substr(0, op)](obj, Number(text.substr(op).trim()));
} else { } else {
@ -323,7 +307,7 @@ overlordService.factory('Tokenizer', ['$filter', function ($filter) {
return {'Col': comparator.Col, 'Comparator': comparators[comparator.Comparator], 'Value': value}; return {'Col': comparator.Col, 'Comparator': comparators[comparator.Comparator], 'Value': value};
} }
function opLength(operator) { function operatorLength(operator) {
var i, var i,
ops = ['<=', '<', '>=', '>', '==', '!=']; ops = ['<=', '<', '>=', '>', '==', '!='];
for (i = 0; i < ops.length; i++) { for (i = 0; i < ops.length; i++) {

View File

@ -417,7 +417,7 @@ var overlord = angular.module('overlord', ['overlord.directive', 'overlord.filte
}]) }])
.config(['$mdDateLocaleProvider', function ($mdDateLocaleProvider) { .config(['$mdDateLocaleProvider', function ($mdDateLocaleProvider) {
$mdDateLocaleProvider.formatDate = function (date) { $mdDateLocaleProvider.formatDate = function (date) {
return moment(date).format('DD-MMM-YYYY'); return !angular.isUndefined(date) ? moment(date).format('DD-MMM-YYYY') : "";
}; };
$mdDateLocaleProvider.parseDate = function (date) { $mdDateLocaleProvider.parseDate = function (date) {
return moment(date, 'DD-MMM-YYYY').toDate(); return moment(date, 'DD-MMM-YYYY').toDate();

View File

@ -1,8 +1,80 @@
'use strict'; 'use strict';
var RecipeListController = ['$scope', 'recipes', function ($scope, recipes) { var RecipeListController = ['$scope', 'recipes', '$location', '$routeParams', 'Tokenizer', function ($scope, recipes, $location, $routeParams, Tokenizer) {
$scope.info = recipes; $scope.info = recipes;
}]; $scope.search = $routeParams.q || '';
$scope.startDate = function (startDate) {
if (arguments.length === 0) {
return $scope._startDate;
}
$scope._startDate = startDate;
var re = /(('[s]'|"[s]"|[s]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi,
matches = $scope.search.match(re),
st = startDate === null ? '' : 's:>=' + moment(startDate).format('DD-MMM-YYYY');
$scope.search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st);
};
$scope.finishDate = function (finishDate) {
if (arguments.length === 0) {
return $scope._finishDate;
}
$scope._finishDate = finishDate;
var re = /(('[f]'|"[f]"|[f]+)\s*:\s*('[^']+'|"[^"]+"|[^\s]+))/gi,
matches = $scope.search.match(re),
st = finishDate === null ? '' : 'f:<=' + moment(finishDate).format('DD-MMM-YYYY');
$scope.search = (matches === null) ? $scope.search.trim() + ' ' + st : $scope.search.replace(re, st);
};
$scope.$watch('search', function (value) {
$scope.filterRecipes(value);
}, true);
$scope.$on('$destroy', function () {
Tokenizer.doFilter.cache = {};
});
$scope.searchInfo = {
comparator: {
'n': {'Col': 'Name', 'Comparator': 'text'},
's': {'Col': 'ValidFrom', 'Comparator': 'date'},
'f': {'Col': 'ValidTo', 'Comparator': 'date'}
},
def: 'n',
sorter: {
'n': 'Name',
's': 'ValidFrom',
'f': 'ValidTo',
'c': 'Costing',
'cp': 'CostPrice',
'sp': 'SalePrice'
},
flags: {}
};
$scope.filterRecipes = _.debounce(function (q) {
if (q !== $scope._search) {
$scope._search = q;
if (angular.isUndefined(q) || q === '') {
$location.path('/Recipes').search('q', null).replace();
} else {
$location.path('/Recipes').search({'q': q}).replace();
}
$scope.$apply(function () {
var matches = Tokenizer.parseFilterString(q, $scope.searchInfo);
$scope.recipes = _.map($scope.info, function (prod) {
var item = angular.copy(prod);
item.Prices = Tokenizer.doFilter(prod.ProductID + ' ' + q, prod.Prices, matches);
return item;
});
});
}
}, 350);
}]
;
RecipeListController.resolve = { RecipeListController.resolve = {
recipes: ['Recipe', function (Recipe) { recipes: ['Recipe', function (Recipe) {