Parse date string to Date object when loading Angular UI Bootstrap Datepicker - json

I'm using Angular UI Bootstrap Datepicker:
https://angular-ui.github.io/bootstrap/#/datepicker
When I render form using data received from the server, there is problem with datetime fields. My input datepicker looks like this:
<form name="itemForm">
<input type="datetime" class="form-control" id="startedAt" name="startedAt"
ng-model="item.startedAt"
ng-click="open($event, 'startedAt')"
uib-datepicker-popup="yyyy-MM-dd"
is-open="datepickers.startedAt"
/>
</form>
My server returns response datetime as JSON string:
{
...
startedAt: "2015-05-29T02:00:00+0200"
}
When I assign response data to the model $scope.item = response;, datepicker input field is rendered correctly (correct date is selected and it's properly formatted in format I selected). The problem is that validation does not pass. I get:
itemForm.startedAt.$invalid == true
I noticed that data bound to the datepicker field should be Date object and not string (when I select new date from the datepicker, $scope.item.startedAt is a Date)
I managed to work around this issue and do this in the controller:
$scope.item = response;
$scope.item.startedAt = new Date($scope.item.startedAt);
It works this way... But I wouldn't like to manually convert string do date every time I get a response from the server. I tried to create a directive, that I can assign to the datepicker input field so it converts the ng-model for me:
.directive("asDate", function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, modelCtrl) {
modelCtrl.$formatters.push(function (input) {
var transformedInput = new Date(input);
if (transformedInput != input) {
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
}
return transformedInput;
});
}
}
})
Well it works, because now I can see Date object, when I output model in my view: {{item.startedAt}}. However still validation fails! I suspect this is some problem with me understanding how data flows between model and the view, and how UI Bootstrap hooks into it.
Also when I change my directive from $formatters.push to $formatters.unshift, validation works OK, but datepicker does not format my datetime (insted of nicely formattet yyyy-MM-dd I see ISO string inside the input)

This broke as of Angular.UI.Bootstrap v0.13.2 (8-2-2015)
Downgrading to 0.13.1 works, which is where I'm stuck today.
Wesleycho says this was done intentionally
https://github.com/angular-ui/bootstrap/issues/4690
I'm ready for other date pickers that support strings if anyone has a suggestion
...soon after posting this I went down a non-angular path that I'm not proud of, but it works for both HTML5 type="date" and uib-datepicker-popup. I have a regular expression that determines if a string resembles one of the two serialized date formats I've seen, and then I have a recursive javascript function to traverse a json tree and replace those strings with Date(). You would call it just before you put in $scope (or viewmodel) ...
$http.get("../api/comm/" + commId
).success(function (resp) {
fixDates(resp);
vm.comm = resp;
});
(I need not check the string length, but I figured it would spare some cpu cycles by not running the regex if the string is obviously not a date)
//2015-10-01T00:00:00-04:00
//2015-11-20T18:15:56.6229516-05:00
var isDate = new RegExp("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{7})?-\\d{2}:00");
function fixDates(json) {
for (i in json)
if (typeof (json[i]) == "object")
fixDates(json[i]);
else if (typeof (json[i]) == "string" && (json[i].length == 25 || json[i].length == 33) && isDate.test(json[i]))
json[i] = new Date(json[i]);
};

As this is intentional behaviour of angular-ui-bootstrap datepicker (https://github.com/angular-ui/bootstrap/issues/4690), I ended up using Angular service/factory and moment library.
Service dateConverter can be injected globally to intercept all HTTP requestes/responses or only in desired controllers.
Here I use Restangular library to handle request to REST API, hence the response.plain() method which takes only object properties, and not Restangular methods/properties.
var Services = angular.module('app.Services', []);
Services
.factory('dateConverter', ['dateFilter', function (dateFilter) {
var dateConverter = {};
dateConverter.prepareResponse = function (response) {
for(prop in response.plain()) {
if (response.hasOwnProperty(prop)) {
if(moment(response[prop], moment.ISO_8601, true).isValid()) {
response[prop] = new Date(response[prop]);
}
}
}
return response;
};
dateConverter.prepareRequest = function (item) {
for(prop in item.plain()) {
if (item.hasOwnProperty(prop)) {
if(angular.isDate(item[prop])){
item[prop] = dateFilter(item[prop] , "yyyy-MM-ddTHH:mm:ssZ")
}
}
}
return item;
};
return dateConverter;
}])
;

you can transform String to Date in restangular transformer, something like this
RestangularConfigurer
.addElementTransformer('<RESTRESOURCENAME>', false, function (element) {
element.createDate = new Date(element.createDate);
return element;
})

Related

How can I send custom object from my callable Firebase Cloud Function in TypeScript to my Unity app?

I'm trying to use Firebase and its callable Cloud Functions for my Unity project.
With the docs and different posts I found on the web I struggle to understand how returning data works. (I come from Azure Functions in C#)
I use TypeScript, and try to return a custom object CharactersResponse:
export class CharactersResponse //extends CustomResponse
{
Code!: CharactersCode;
CharacterID?: string;
}
export enum CharactersCode
{
Success = 0,
InvalidName = 2000,
CharacterNameAlreadyExists = 2009,
NoCharacterSlotAvailable = 3000,
InvalidCharacterClass = 4000,
EmptyResponse = 9000,
UnknownError = 9999,
}
(Custom Response is a parent class with only an UnknownErrorMessage string property, that I use for adding extra message when needed, but only in Unity. I don't need it in my functions.)
I have the same in my C# Unity Project:
public class CharactersResponse : CustomResponse
{
public CharactersCode Code;
public string CharacterID;
}
public enum CharactersCode
{
Success = 0,
InvalidName = 2000,
CharacterNameAlreadyExists = 2009,
NoCharacterSlotAvailable = 3000,
InvalidCharacterClass = 4000,
EmptyResponse = 9000,
UnknownError = 9999,
}
I'm still learning but I found it useful to do this way for displaying correct messages in Unity (and also regarding localization).
When the Code is 0 (Success), I will usually need to get some data at the same time like in this example CharacterID, or CharacterLevel, CharacterName etc.. CharacterResponse will be used for all functions regarding Characters like "GetAllCharacters", "CreateNewCharacter" etc..
My Function (CreateNewCharacter) looks like this:
import * as functions from "firebase-functions";
import { initializeApp } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";
import { CharactersResponse } from "./CharactersResponse";
import { CharactersCode } from "./CharactersResponse";
import { StringUtils } from "../Utils/StringUtils";
// DATABASE INITIALIZATION
initializeApp();
const db = getFirestore();
// CREATE NEW CHARACTER
export const CreateNewCharacter =
functions.https.onCall((data, context) =>
{
// Checking that the user is authenticated.
if (!context.auth)
{
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
}
// TEST
data.text = '';
// Authentication / user information is automatically added to the request.
const uid: string = context?.auth?.uid;
const characterName: string = data.text;
// Check if UserID is present
if (StringUtils.isNullOrEmpty(uid))
{
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'Missing UserID in Auth Context.');
}
const response = new CharactersResponse();
if (StringUtils.isNullOrEmpty(characterName))
{
response.Code = CharactersCode.InvalidName;
console.log("character name null or empty return");
return response; // PROBLEM IS HERE *****************
}
console.log("end return");
return "Character created is named : " + characterName + ". UID = " + uid;
});
In Unity, the function call looks like this:
private static FirebaseFunctions functions = FirebaseManager.Instance.Func;
public static void CreateNewCharacter(string text, Action<CharactersResponse> successCallback, Action<CharactersResponse> failureCallback)
{
Debug.Log("Preparing Function");
// Create the arguments to the callable function.
var data = new Dictionary<string, object>();
data["text"] = text;
// Call the function and extract the operation from the result.
HttpsCallableReference function = functions.GetHttpsCallable("CreateNewCharacter");
function.CallAsync(data).ContinueWithOnMainThread((task) =>
{
if (task.IsFaulted)
{
foreach(var inner in task.Exception.InnerExceptions)
{
if (inner is FunctionsException)
{
var e = (FunctionsException)inner;
// Function error code, will be INTERNAL if the failure
// was not handled properly in the function call.
var code = e.ErrorCode;
var message = e.Message;
Debug.LogError($"Code: {code} // Message: {message}");
if (failureCallback != null)
{
failureCallback.Invoke(new CharactersResponse()
{
Code = CharacterCode.UnknownError,
UnknownErrorMessage = $"ERROR: {code} : {message?.ToString()}"
});
}
}
}
}
else
{
Debug.Log("About to Deserialize response");
// PROBLEM IS HERE *********************
CharactersResponse response = JsonConvert.DeserializeObject<CharactersResponse>(task.Result.Data.ToString());
Debug.Log("Deserialized response");
if (response == null)
{
Debug.LogError("Response is NULL");
}
else
{
Debug.Log("ELSE");
Debug.Log($"Response: {response}");
Debug.Log(response.Code.ToString());
}
}
});
}
The problem :
In my Unity C# code, task.Result.Data contains the CharactersCode I've set in my function, but I can't find a way to convert it to CharactersResponse. (It worked in Azure Functions). Moreover, the line just after Deserialization Debug.Log("Deserialized response"); is not executed. The code seems stuck in the deserialization process.
I tried with and without extending my TypeScript class with CustomResponse(because I don't need it in my Function so I didn't extended it at first).
I also tried setting a CharacterID because I thought maybe it didn't like the fact that this property was missing but the result is the same.
I don't understand what is the problem here? If any of you can help.
Thanks.
HttpsCallableResult.Data is of type object!
=> Your ToString will simply return the type name something like
System.Object
or in your case the result is a dictionary so it prints out that type.
=> This is of course no valid JSON content and not what you expected.
Simply construct the result yourself from the data:
var result = (Dictionary<string, object>)task.Result.Data;
CharactersResponse response = new CharactersResponse
{
Code = (CharactersCode)(int)result["Code"],
CharacterID = (string)result["CharacterID"];
};
I wanted to implement derHugo's solution but couldn't find a way to convert task.Result.Data to Dictionary<string, object>.
The code was stuck at var result = (Dictionary<string, object>)task.Result.Data; even in step by step debugging and no error popped up.
OLD SOLUTION:
So I did a little research and stumbled upon this post and ended up using this instead :
var json = JsonConvert.SerializeObject(task.Result.Data);
CharactersResponse response = JsonConvert.DeserializeObject<CharactersResponse>(json);
I basically convert the task.Result.Data to JSON and convert it back to CharactersResponse and it works. I have what I wanted.
However, I seem to understand that it is not the best solution performance-wise, but for now it is okay and I can now move forward in the project, I'll try to find a better solution later.
NEW SOLUTION:
I wanted to try one last thing, out of curiosity. I wondered what if I convert to JSON at the beginning (in my function) instead of at the end (in my Unity app). So I did this in my function's TypeScript code:
response.Code = CharactersCode.InvalidName;
var r = JSON.stringify(response); // Added this line
return r; // return 'r' instead of 'response'
In my C# code, I retried this line of code:
CharactersResponse response = JsonConvert.DeserializeObject<CharactersResponse>(task.Result.Data.ToString());
And it works ! I just needed to convert my object to JSON in my function before returning it. It allows me to "save" one line of code to process on the client side compared to the old solution.
Thanks derHugo for your answer as it helped me finding what I want.

AngularJS $http and filters

I have a JSON file, which contains:
{
"/default.aspx": "headerBg",
"/about.aspx": "aboutBg",
"/contact.aspx": "contactBg",
"/registration.aspx": "regBg",
"/clients.aspx": "clientsBg",
"/onlinesessions.aspx": "bg-white-box",
"/ondemamdsessions.aspx": "bg-grey"
}
Now I am reading this json file using $http, but I want to add a filter in below fashion:
Using window.location.pathname, I am reading path of the current page, suppose the current page is /about.aspx
Then I want to add a filter in $http response by which I want to read only aboutBg.
The code I wrote can retrieve all the values, but unable to filter that. Please help.
User this function where you receive the response.
function getPageBgClass(currentPage, responseData) {
if (responseData.hasOwnProperty(currentPage))
return responseData[currentPage]
else
return "none"
}
Here is how it should be used in your promise then function
function(response) {
var bg = getPageBgClass(window.location.pathname, response.data);
//Your code here ...
}
there is no direct method to get key using value from json.
you should make sure that there are no 2 keys having same value for below code to work
function swapJsonKeyValues(input) {
var one, output = {};
for (one in input) {
if (input.hasOwnProperty(one)) {
output[input[one]] = one;
}
}
return output;
}
var originaJSON = {
"/default.aspx": "headerBg",
"/about.aspx": "aboutBg",
"/contact.aspx": "contactBg",
"/registration.aspx": "regBg",
"/clients.aspx": "clientsBg",
"/onlinesessions.aspx": "bg-white-box",
"/ondemamdsessions.aspx": "bg-grey"
}
var invertedJSON = swapJsonKeyValues(originaJSON);
var samplepathname = "aboutBg";
var page = invertedJSON[samplepathname];
[function swapJsonKeyValues from https://stackoverflow.com/a/1970193/1006780 ]

Access JSON Object Prop - Angular JS

First time using Angular JS, I'm using $http.get to return a Json object. When I output the response data, I can see entire JSON object in the console, but I can't access the object or properties. What am I missing here?
$scope.init = function (value) {
$scope.productEditorModel.productId = value;
$scope.loadData($scope.productEditorModel.productId);
}
$scope.loadData = function (productId) {
var responsePromise = $http.get("/Product/GetProductModel", {
params: { productId: productId }
});
responsePromise.success(function (dataFromServer, status, headers, config) {
console.log(dataFromServer.DataModel);
});
};
When I first output the dataFromServer to the console, the object is null and then it becomes populated. Since it's an async call, I should be able to access and set whatever vars inside the success
I would like to be able to directly access the object and property names IE:
$scope.productModel.productId = dataFromServer.Data.productId
My json looks like this:
Object{DataModel:Object, IsSuccess: false}
Thanks!
The problem is that you are trying to access the data before it comes back. Here is a plunker that demonstrates how to set it up, and how not to.
//predefine our object that we want to stick our data into
$scope.myDataObject = {
productId: 'nothing yet',
name: 'nothing yet'
}
//get the data, and when we have it, assign it to our object, then the DOM will automatically update
$http.get('test.json')
.success(function(data) {
$scope.myDataObject = data
});
var y = $http.get('test.json')
//this throws an error because I am trying to access the productId property of the promise object, which doesn't exist.
console.log(y.productId);
Here is the demo

Select2 and JSON Data

I am using select2 version 4 and I have a REST service control on an XPage, that reads the fullname column from the names.nsf.
I have the search working, but for some reason, I don't get a list of values back I can select.
The JSON object being returned, looks something like this:
[{"#entryid":"1376-E6D5EBE8ADBEFA7088257DF8006E4BA2","fullname":"Full Name\/OU\/O"},{"#entryid":"1375-FD1CB92A13BFD0E088257DE4006756D7","fullname":"Another Full Name\/OU\/O"}]
The code to initialize the select2 looks like this:
x$( "#{id:comboBox1}" ).select2({
ajax: {
url: "xJSON.xsp/names",
dataType: 'json',
delay: 250,
data: function (params) {
return {
search:'[fullname=]*'+params.term+'*',
// q: params.term, // search term
page: params.page
};
},
results: function (data, page){
},
processResults: function (data, page) {
// parse the results into the format expected by Select2.
// since we are using custom formatting functions we do not need to
// alter the remote JSON data
console.log(data);
return {
results: data
};
},
cache: true
},
//escapeMarkup: function (markup) { return markup; },
minimumInputLength: 1
});
When I look at the browser's console, I can see that the search worked and JSON objects are being returned, however, I don't get a list of values to select from.
For the result return I've tried results: data.fullname and results: data, text:'fullname' but nothing happens.
What am I doing worng?
You need to either switch your JSON response to include id and text for each object, or re-map them in your processResults method. These two properties are required on all selectable objects now in Select2 4.0. Since I'm assuming you either can't change your JSON response, or it wouldn't make sense to, you can easily re-map the data with the following processResults method.
processResults: function (data) {
var data = $.map(data, function (obj) {
obj.id = obj.id || obj["#entityid"];
obj.text = obj.text || obj.fullname;
return obj;
});
return {
results: data
};
});
This will map the #entityid property to the id property and the fullname property to the text property. So selections will be sent to your server containing the #entityid and will be displayed using the fullname.
Also, the results method is no longer needed in Select2 4.0. This was renamed to the current processResults method.
I copied your code exactly as it is, and just changed the fieldname and the search query and worked out just fine.
This is my JSON looks like
[{"#entryid":"1482-AD112B834158AD0D80257E4B004EC42E","#unid":"AD112B834158AD0D80257E4B004EC42E","id":"Victor Hunter","text":"Odhran Patton"},{"#entryid":"1496-291F2480D806A91E80257E4B004EC3D2","#unid":"291F2480D806A91E80257E4B004EC3D2","id":"Wesley O'Meara","text":"Wesley O'Meara"},{"#entryid":"1421-CC19D06880F5DC2980257E4B004EC537","#unid":"CC19D06880F5DC2980257E4B004EC537","id":"Stephen Woods","text":"Emma Doherty"}]
What I know is that select2 expects an id, and a text parameters from the JSON.

Angular service/factory return after getting data

I know this has something to do with using $q and promises, but I've been at it for hours and still can't quite figure out how it's supposed to work with my example.
I have a .json file with the data I want. I have a list of people with id's. I want to have a service or factory I can query with a parameter that'll http.get a json file I have, filter it based on the param, then send it back to my controller.
angular
.module("mainApp")
.controller('personInfoCtrl',['$scope', '$stateParams', 'GetPersonData', function($scope, $stateParams, GetPersonData) {
$scope.personId = $stateParams.id; //this part work great
$scope.fullObject = GetPersonData($stateParams.id);
//I'm having trouble getting ^^^ to work.
//I'm able to do
//GetPersonData($stateParams.id).success(function(data)
// { $scope.fullObject = data; });
//and I can filter it inside of that object, but I want to filter it in the factory/service
}]);
Inside my main.js I have
//angular.module(...
//..a bunch of urlrouterprovider and stateprovider stuff that works
//
}]).service('GetPersonData', ['$http', function($http)
{
return function(id) {
return $http.get('./data/people.json').then(function(res) {
//I know the problem lies in it not 'waiting' for the data to get back
//before it returns an empty json (or empty something or other)
return res.data.filter(function(el) { return el.id == id)
});
}
}]);
The syntax of the filtering and everything works great when it's all in the controller, but I want to use the same code in several controls, so I'm trying to break it out to a service (or factory, I just want the controllers to be 'clean' looking).
I'm really wanting to be able to inject "GetPersonData" to a controller, then call GetPersonData(personId) to get back the json
You seems to be syntax issue in your filter function in the service.
.service('GetPersonData', ['$http', function($http){
return function(id) {
return $http.get('./data/people.json').then( function (res) {
return res.data.filter(function(el) { return el.id == id });
});
}}]);
But regarding the original issue you cannot really access the success property of the $q promise that you are returning from your function because there is no such property exist, It exists only on the promise directly returned by the http function. So you just need to use the then to chain it through in your controller.
GetPersonData($stateParams.id).then(function(data){ $scope.fullObject = data; });
If you were to return return $http.get('./data/people.json') from your service then you will see the http's custom promise methods success and error.