Is there any way to get ISO-dates parsed as javascript-dates om Backbone models.
Let's say some JSON returns model data like this:
{ prop1: "somevalue", date: "2011-05-11T18:30:00" }
To make this work as a date, i came up with this:
var Model = exports.Model = Backbone.Model.extend({
toJSON: function () {
return _.extend(this.attributes, {
date: new Date(this.attributes.date)
});
}
});
This works fine, and makes it possible to write template-markup as this:
"<%=date.toLocaleDateString() %>"
Its all fine, but couldn't it be done at the BackboneModel prototype somehow. This way i have to write this implementation, on every model property of type date - thats not very DRY
Because the JSON ISO8601-formatted dates are just strings, there is no magical way of just parsing them. You need to either declare or detect the fields which should be parsed.
Declaration is easy, just define an array of date fields on the model an parse each of them.
//declare dateFields on the model
var TestModel = Backbone.Model.extend({
dateFields: [
'createdDate',
'updatedDate'
]
});
//override toJSON
Backbone.Model.prototype.toJSON = function() {
return this._parseDates(this.attributes);
};
//hydrates string dates to Date objects
Backbone.Model.prototype._parseDates = function(attrs) {
attrs = _.clone(attrs);
if(!this.dateFields) { return attrs; }
_.each(this.dateFieds, function(field) {
attrs[field] = new Date(attrs[field]);
});
return atts;
};
That requires you to tell the date parser which fields to consider. Since you were hoping for something more automagical, the other option is to try to detect which fields are dates by looking at the shape of each string value:
//hydrates string dates to Date objects
Backbone.Model.prototype._parseDates = function(attrs) {
attrs = _.clone(attrs);
var iso8601Pattern = /^[0-9][0-9][0-9][0-9](-[0-1][0-9](-[0-3][0-9](T[0-9][0-9](:[0-9][0-9](:[0-9][0-9])(\.[0-9][0-9][0-9]?)?)?)?)?)?Z$/;
_.each(attrs, function(value, key) {
if(_.isString(value) && iso8601pattern.test(value)) {
attrs[key] = new Date(value);
}
});
return attrs;
};
Which doesn't require you to list all the date fields, but will add overhead to parsing, because it needs to test each value to see if it looks like a date.
Related
I'd like to implement a filter that takes in up to say 3 attributes and returns result that matches all of them on the .json input, also known as AND filter.
I'm wondering if there is a way to pass the res (result) of my first filter as the input when the second filter is applied. In other words, I'd like to 'change the subject' from the original collection I iterated on to the filtered result, and keep doing that for every added filter.
Please do let me know if there is another practice for using complex filters.
Below is my code:
angular.module('app')
.filter('categoryFilter', function () {
return function (input, ctrl) {
if(ctrl.filtersModel) {
// 1 filter: ctrl.filtersModel > {brand: "bmw"}
// 2 filters: ctrl.filtersModel > {brand: "bmw", speed: "medium"}
var res = [];
angular.forEach(input, function(car){
angular.forEach(ctrl.filtersModel, function(value, key){
if(car[key] === value) {
// this won't obviously work:
res.push(car);
// if I was able apply first filter, then save the result and use it as an input when then 2nd filter is applied, that would work
}
});
});
return res;
}
else {
return input;
}
};
});
I am successfully calling $http.get in my Angular controller and getting back a large json object. Like this:
var self=this;
self.createNewStatusReport = function()
{
$http.get(self.NewStatusReportUrl).then(function(response)
{
self.AngularModel.StatusReportJsons.push(response.data);
});
};
The returned json includes many dates. The format, unfortunately, looks like this: /Date(1420099200000)/. Here's a simplified piece of the response data:
{
"StatusReportID": 25,
"DueDate": "/Date(1468566000000)/",
"SubmitDate": null,
"WorkStatement": [
{
"WorkStatementID": 41,
"Milestone": [
{
"MilestoneID": 501,
"ContractorComments": null,
"Title": "Do some specific piece of work",
"StartDate": "/Date(1459494000000)/",
"EndDate": "/Date(1469948400000)/",
"IsCompleted": false,
...
I also have (some) control over the server side, but can't change the date types in StatusReportJson from DateTime? to string. It is MVC written in C#:
[HttpGet]
public JsonResult NewStatusReport(string agreementNumber)
{
var statusReport = StatusReports.GetStatusReport(25);
return Json(new StatusReportJson(statusReport), JsonRequestBehavior.AllowGet);
}
Is there an easy way to recursively convert these date strings to date objects? The response data comes to me already parsed; can I insert my own parse step? On the server side, can I make the dates come in as date strings that look more like "2016-04-01T00:00:00" or simply "2016-04-01" without modifying my StatusReportJson object's data types? Others have already solved the conversion problem here: How do I format a Microsoft JSON date? I need help structuring where to put the solution so it is effective in my case. Thanks for helping!
Hope this solves your problem:
$scope.DateIssue = function(input) {
input = '/Date(1468566000000)/';
$scope.formatedDate = input.toString().replace('/Date(', '').replace(')/', '');
$scope.formatedDate = $filter('date', $scope.formatedDate);
return $scope.formatedDate
};
Here's how I solved this. First, I used the JavaScript serializer on the server side like this:
[HttpGet]
public JsonResult NewStatusReport(string agreementNumber)
{
var statusReport = StatusReports.GetStatusReport(25);
var statusReportJson = new StatusReportJson(statusReport);
var json = new JavaScriptSerializer().Serialize(statusReportJson);
return Json(json, JsonRequestBehavior.AllowGet);
}
Then, on the client side, I pulled in code from this excellent page http://erraticdev.blogspot.com/2010/12/converting-dates-in-json-strings-using.html and called it like this:
var self = this;
$http.get(self.NewStatusReportUrl).then(function(response)
{
var jsonObject = jQuery.parseJSON(response.data, true);
self.AngularModel.StatusReportJsons.push(jsonObject);
});
Thanks to Robert Koritnik for the answer! And thanks to everyone who helped!
A little late, but I thought helpful.
Most of the suggestions were to locally convert it. In my case the date is returned as string (with Timezone info).
E.g. '2018-06-01T13:57:41.3867449Z'
So I created a common service for getJson & PostJson and in handled responses there with '$q'.
if (response.data) {
// Check for datetime object & convert
// TODO:: *** May impact performance, so check later or use momentJS
//console.info('Before-convertDateStringsToDates:', new Date());
appUtils.convertDateStringsToDates(response.data);
//console.info('After-convertDateStringsToDates:', new Date());
}
My appUtil method is as below:
// --------------------------------------------------------------------------------
// Ref: http://aboutcode.net/2013/07/27/json-date-parsing-angularjs.html
// Function to convert string (as ReGex format) property to date - used as generic in Common Serivce.
convertDateStringsToDates(input) {
// ReGex for format we receive from Web API e.g. "1980-05-09T00:00:00Z"
var jsonDateTimeFormatRegex = "((?:2|1)\\d{3}(?:-|\\/)(?:(?:0[1-9])|(?:1[0-2]))(?:-|\\/)(?:(?:0[1-9])|(?:[1-2][0-9])|(?:3[0-1]))(?:T|\\s)(?:(?:[0-1][0-9])|(?:2[0-3])):(?:[0-5][0-9]):(?:[0-5][0-9]))";
// Ignore things that aren't objects.
if (typeof input !== "object") return input;
for (var key in input) {
if (!input.hasOwnProperty(key)) continue;
var value = input[key];
var match;
// Check for string properties which look like dates.
// TODO: Improve this regex to better match ISO 8601 date strings.
if (typeof value === "string" && (match = value.match(jsonDateTimeFormatRegex))) {
// Assume that Date.parse can parse ISO 8601 strings, or has been shimmed in older browsers to do so.
//console.info(match[0]);
var date = new Date(match[0]); // Need to convert to UTC. Ref: https://stackoverflow.com/a/14006555/1161069
input[key] = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds()));
// var milliseconds = Date.parse(match[0]);
// if (!isNaN(milliseconds)) {
// input[key] = new Date(milliseconds);
// }
} else if (typeof value === "object") {
// Recurse into object
this.convertDateStringsToDates(value);
}
}
}
Now, after each GET or POST request, I get my JSON with proper dates.
Just in case someone wants to know Web API code, it's as below:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
//var cors = new System.Web.Http.Cors.EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
// Web API routes
config.MapHttpAttributeRoutes();
// other code ......
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
//jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
JsonSerializerSettings jSettings = new JsonSerializerSettings()
{
Formatting = Formatting.None
};
jSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
jsonFormatter.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;
jsonFormatter.SerializerSettings = jSettings;
// rest of the code....
}
(was not sure what to have as a title, so if you have a better suggestion, feel free to come up with one - I will correct)
I am working on an angular application where I have some menues and a search result list. I also have a document view area.
You can sort of say that the application behaves like an e-mail application.
I have a few controllers:
DateCtrl: creates a list of dates so the users can choose which dates they want to see posts from.
SourceCtrl: Creates a list of sources so the user can choose from which sources he/she wants to see posts from.
ListCtrl: The controller populating the list. The data comes from an elastic search index. The list is updated every 10-30 seconds (trying to find the best interval) by using the $interval service.
What I have tried
Sources: I have tried to make this a filter, but a user clicks two checkboxes the list is not sorted by date, but on which checkbox the user clicked first.
If it is possible to make this work as a filter, I'd rather continue doing that.
The current code is like this, it does not do what I want:
.filter("bureauFilter", function(filterService) {
return function(input) {
var selectedFilter = filterService.getFilters();
if (selectedFilter.length === 0) {
return input;
}
var out = [];
if (selectedFilter) {
for (var f = 0; f < selectedFilter.length; f++) {
for (var i = 0; i < input.length; i++) {
var myDate = input[i]._source.versioncreated;
var changedDate = dateFromString(myDate);
input[i]._source.sort = new Date(changedDate).getTime();
if (input[i]._source.copyrightholder === selectedFilter[f]) {
out.push(input[i]);
}
}
}
// return out;
// we need to sort the out array
var returnArray = out.sort(function(a,b) {
return new Date(b.versioncreated).getTime() - new Date(a.versioncreated).getTime();
});
return returnArray;
} else {
return input;
}
}
})
Date: I have found it in production that this cannot be used as a filter. The list of posts shows the latest 1000 posts, which is only a third of all posts arriving each day. So this has to be changed to a date-search.
I am trying something like this:
.service('elasticService', ['es', 'searchService', function (es, searchService) {
var esSearch = function (searchService) {
if (searchService.field === "versioncreated") {
// doing some code
} else {
// doing some other type of search
}
and a search service:
.service('searchService', function () {
var selectedField = "";
var selectedValue = "";
var setFieldAndValue = function (field, value) {
selectedField = field;
selectedValue = value;
};
var getFieldAndValue = function () {
return {
"field": selectedField,
"value": selectedValue
}
};
return {
setFieldAndValue: setFieldAndValue,
getFieldAndValue: getFieldAndValue
};
})
What I want to achieve is this:
When no dates or sources are clicked the whole list shall be shown.
When Source or Date are clicked it shall get the posts based on these selections.
I cannot use filter on Date as the application receives some 3000 posts a day and so I have to query elastic search to get the posts for the selected date.
Up until now I have put the elastic-search in the listController, but I am now refactoring so the es-search happens in a service. This so the listController will receive the correct post based on the selections the user has done.
Question is: What is the best pattern or method to use when trying to achieve this?
Where your data is coming from is pretty irrelevant, it's for you to do the hook up with your data source.
With regards to how to render a list:
The view would be:
<div ng-controller='MyController as myCtrl'>
<form>
<input name='searchText' ng-model='myCtrl.searchText'>
</form>
<ul>
<li ng-repeat='item in myCtrl.list | filter:myCtrl.searchText' ng-bind='item'></li>
</ul>
<button ng-click='myCtrl.doSomethingOnClick()'>
</div>
controller would be:
myApp.controller('MyController', ['ElasticSearchService',function(ElasticSearchService) {
var self = this;
self.searchText = '';
ElasticSearchService.getInitialList().then(function(list) {
self.list = list;
});
self.doSomethingOnClick = function() {
ElasticSearchService.updateList(self.searchText).then(function(list) {
self.list = list;
});
}
}]);
service would be:
myApp.service('ElasticSearchService', ['$q', function($q) {
var obj = {};
obj.getInitialList = function() {
var defer = $q.defer();
// do some elastic search stuff here
// on success
defer.resolve(esdata);
// on failure
defer.reject();
return defer.promise();
};
obj.updateList = function(param) {
var defer = $q.defer();
// do some elastic search stuff here
// on success
defer.resolve(esdata);
// on failure
defer.reject();
return defer.promise();
};
return obj;
}]);
This code has NOT been tested but gives you an outline of how you should approach this. $q is used because promises allow things to be dealt with asynchronously.
I have created a custom filter to filter images that i get from MySql using php.
Then i pass the array into angular using encode and what i get is $scope.pics which has ID,pic_name and upload_date for each entry.
The custom filter works well as i can see it in the console . The problem is i am confused regarding how and where to call the filter .
I need to send the date range(that gets updated in the directive)into the filter
.
my directive :
app.directive('datepicker', function(){
return {
require: 'ngModel',
link : function(scope,element,attrs, ngModel){
element.datepicker({
dateFormat:'dd/mm/yy',
changeMonth: true,
changeYear: true,
yearRange: '1950:2015',
yearRange: "-100:+0",
onSelect: function(dateText) {
scope.$apply(function() {
ngModel.$setViewValue(dateText);
scope.helloFilter(scope.from,scope.to);//calling filter function- defined in controller ,also from and to are my ng-models in my view for the two dates
});
},
});
}
}});
my controller:
app.controller('MyController',function($scope,$http,$filter) {
.......
$scope.helloFilter = function(from,to){
return $filter('dateFilter')($scope.pics,from,to);
};
my view: (its messy but i need help with just the date filter part !)
<div ng-repeat="pic in pics|filter:{pic_name:n}|filter : dateFilter ">
my filter:
app.filter('dateFilter', function(){
return function(arr,from,to) {
var a =[];// to store upload dates from the recieved array 'arr'
var arrNew = [];//array to store original array
var picFilter = [];// to store filered array
var i = 0;
for(i=0;i<arr.length;i++){
arrNew[i] = arr[i];//initialising to original array
}
if(from == null){ // if user doesnt provide start date
from = moment("01011970","DD MM YYYY").format('MM DD YYYY');
}
f = moment((from),'DD MM YYYY').format('YYYY/MM/DD');//format input date to compare with MySql date
t = moment((to),'DD MM YYYY').format('YYYY/MM/DD');
var cnt=0;// to enter filtered data into array to be returned
for(i=0;i<arr.length;i++){
a[i] = arr[i].upload_date;//extracting date to compare
con1 = moment(a[i],'YYYY MM DD').isAfter(f);
con2 = moment(a[i],'YYYY MM DD').isBefore(t);
if(con1 && con2){// if the dates in database lie between the range
picFilter[cnt] = arr[i];//store the filtered array
cnt++;
}
}
if (picFilter.length>0){
arrNew.length=0;
for(i=0;i<picFilter.length;i++){
arrNew[i] = picFilter[i];//store filtered array
}
} return arrNew;
}
});`
The problem is :
The console shows the filter has worked perfectly .My issues :
1. Am i applying the date filter correctly?? (The 1st filter for pic_name works properly)
2.I dont know what happens to my returned array ? How do i get it reflected in my view?
3. If instead of
$scope.helloFilter = function(from,to){
return $filter('dateFilter')($scope.pics,from,to);
};
I write :
$scope.helloFilter = function(from,to){
$scope.pics = $filter('dateFilter')($scope.pics,from,to);
};
My original array gets modified.
My model updates are not updated in the view. I also tried using return true.Then used $scope.$apply(),also $timeout !! none of them seem to work .
I am new to angularjs and I have a number of doubts related to its basics:-( Pls help !!
I have a Backbone Model in which there are certain properties like
test_id
test_name
test_desc
test_score
Now I want to retrieve properties which are starting with "test_".
I tried with code below and its working fine.
var MyModel = Backbone.Model.extend({
getTestProperties: function(str){
// get clone of attributes to iterate over
var testProperties = {};
var attrs = _.clone(this.attributes);
_.each(attrs, function(val, key){
if(key.indexOf(str) == 0){
testProperties[key]= val;
}
}, this);
}
});
But
Is there any other way I can get these properties using underscore methods ?
Thanks
Backbone proxies some methods from Underscore on models that can help you create a more readable _.filter: _.keys and _.pick
You can then simplify your function like this :
var MyModel = Backbone.Model.extend({
getTestProperties: function (str) {
// get the keys you want
var keys = _.filter(this.keys(), function (key) {
return key.indexOf(str) === 0;
});
// and build an object
return this.pick(keys);
}
});
And a demo http://jsfiddle.net/nikoshr/5a63c/
Try something like
var attrs = _.filter(_.keys(_.clone(this.attributes)), function(attr){
return attr.indexOf("text_") === 0;
});