ExpressJS/AngularJS : Converting JSON Object to String - json

I'm struggling with something that might be very easy, but hours of searching on Stackoverlow haven't helped.
I'm using Sir Trevor with MEANJS. Sir Trevor applies itself against a textarea field and saves its content as a JSON String.
Sir Trevor is saving its content to a field Content, which is set as an Object type in a Mongoose schema.
Creating works great and everything saves as expected.
However, when editing, the data doesn't come up correctly. The textfield is assigned data-ng-model="article.content" [the 'content' field from the model], but displays as [object Object], so when Sir Trevor tries to parse the value, it errors out.
I've tried using a directive with $formatters to change the value:
<textarea data-ng-model="article.content" id="content"
class="form-control st-instance" placeholder="Content" stRaw>
</textarea>
...and here is the directive:
articleApp.directive('stRaw', function(){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attr, ngModel) {
function stringIt(val) {
return JSON.stringify(val);
}
ngModel.$formatters.push(stringIt);
}
};
});
But it doesn't seem like the directive is ever triggered [I tried console.log within the link function and never saw anything].
I was able to make it work fine before by changing the schema type String and then using stringify over and over again within the code. This seemed sloppy, created undue bloat and also created challenges when trying to iterate through on an actual view page [it was seen as a String -- couldn't figure out how to parse].
I'm assuming I need to somehow catch the article.content attribute before it's rendered and change the value to a string. Is that the right direction?

There is typo in your html, the stRaw should be st-raw.
<textarea data-ng-model="article.content" id="content" class="form-control st-instance" placeholder="Content" st-raw></textarea>

Related

ember hasDirtyAttributes do not work with object (json)

I have a model:
export default Model.extend({
title: attr('string'),
attributes: attr('jsonb')
});
Where attributes is a custom json filed stored as jsonb in Postgres.
let say:
{
"name":"Bob",
"city":""
}
So I can easily manipulate attributes using template
<form.element .. #property="attributes.city"/> or model.set('attributes.city','city name')
Problem: hasDirtyAttributes do not changing because technically we have old object. But when I try to copy object let say
JSON.parse(JSON.stringify(this.get('attributes')) hasDirtyAttributes works as expected
So how to write some Mixin for a Model or other workaround which on the change of any attribute property will mark hasDirtyAttributes as true. I will update whole object so doesn't matter which property actually was changed.
Same problem: https://discuss.emberjs.com/t/hasdirtyattributes-do-not-work-with-nested-attributes-json-api/15592
existing solution doesn't work for me at all:
ember-dirtier
ember-data-relationship-tracker
ember-data-model-fragments (a lot of changes under the hood and broke my app)
Update:
Some not perfect idea that help better describe what I'm want to achieve:
Let say we adding observer to any object fileds:
export default Model.extend({
init: function(){
this._super();
this.set('_attributes', Object.assign({}, this.get('attributes'))); //copy original
Object.keys(this.get('attributes')).forEach((item) => {
this.addObserver('attributes.'+ item, this, this.objectObserver);
});
}
...
})
And observer:
objectObserver: function(model, filed){
let privateFiled = '_' + filed;
if (model.get(privateFiled) != model.get(filed)) { //compare with last state
model.set(privateFiled, this.get(filed));
model.set('attributes', Object.assign({}, this.get('attributes')) );
}
}
It's works, but when I change one filed in object due to copying object objectObserver faired again on every filed. So in this key changing every filed in object I mark observed filed as dirty
The further ember development will reduce using of event listener and two-way binding, actually Glimmer components supports only One-way Data Flow. So to be friendly with future versions of emberusing one-way data flow is good approach in this case to. So In my case as I use ember boostrap solution looks like
<form.element #controlType="textarea" #onChange={{action 'attributeChange'}}
where attributeChange action do all works.
New Glimmer / Octane style based on modifier and looks like:
{{!-- templates/components/child.hbs --}}
<button type="button" {{on "click" (fn #onClick 'Hello, moon!')}}>
Change value
</button>

Angular 4 html for loop displaying loosely typed object (string) normally but not when element is extracted directly?

I'm using Angular 4 to develop an app which is mainly about displaying data from DB and CRUD.
Long story short I found that in Angular 4 the component html doesn't like displaying loosely typed object (leaving the space blank while displaying other things like normal with no warning or error given in console) even if it can be easily displayed in console.log output, as shown in a string.
So I made a function in the service file to cast the values into a set structure indicating they're strings.
So now something like this works:
HTML
...
<div>{{something.value}}</div>
...
Component.ts
...
ngOnInit() {
this.route.params.subscribe(params => {
this.pkey = params['pkey'];
this.service.getSomethingById(this.pkey)
.then(
something => {
this.something = this.service.convertToStructure(something);
},
error => this.errorMessage = <any>error);
});
}
...
Code of the function convertToStructure(something)
convertToStructure(someArr: myStructure): myStructure {
let something: myStructure = new myStructure();
something.value = someArr[0].value;
return something;
}
But as I dig into other files for copy and paste and learn skills from what my partner worked (we're both new to Angular) I found that he did NOT cast the said values into a fixed structure.
He thought my problem on not being able to display the values (before I solved the problem) was because of me not realizing it was not a plain JSON object {...} but an array with a single element containing the object [{...}] .
He only solved half of my problem, cause adding [0] in html/component.ts was not able to make it work.
Component.ts when it did NOT work
...
ngOnInit() {
this.route.params.subscribe(params => {
this.pkey = params['pkey'];
this.service.getSomethingById(this.pkey)
.then(
something => {
console.log(something[0].value); //"the value"
this.something = something[0]; //html can't find its value
},
error => this.errorMessage = <any>error);
});
}
...
HTML when it did NOT work
...
<div>{{something[0].value}}</div> <!--Gives error on the debug console saying can't find 'value' of undefined-->
...
And of course when I'm using the failed HTML I only used this.something = something instead of putting in the [0], and vice versa.
So I looked into his code in some other page that display similar data, and I found that he used *ngFor in html to extract the data and what surprised me is that his html WORKED even if both of our original data from the promise is identical (using the same service to get the same object from sever).
Here's what he did in html:
...
<div *ngFor="let obj of objArr" ... >
{{obj.value}}
</div>
...
His html worked.
I'm not sure what happened, both of us are using a raw response from the same service promise but using for loop in html makes it automatically treat the value as strings while me trying to simply inject the value fails even if console.log shows a double quoted string.
What's the difference between having the for loop and not having any for loop but injecting the variable into html directly?
Why didn't he have to tell Angular to use the set structure indicating the values are strings while me having to do all the trouble to let html knows it's but a string?
The difference here is as you said that your JSON is not simple object , its JSON Array and to display data from JSON array you need loop. So, that is why your friends code worked and yours did not. And please also add JSON as well.

Is it possible to have AngularJS internationalize numeric inputs?

We're trying to have AngularJS internationalize <input type='number' /> values.
We've included the localization file (e.g. angular-locale_it-it.js), but the values are still displayed using the English locale.
This is a problem because our back-end (and management) expects numeric values to be in the user's locale, and receiving 123.45 instead of 123,45 causes an error.
You can find an example on jsFiddle.
It works in Chrome 27
but it's not working in Firefox 21 and Internet Explorer 10.
This may not be the answer you want, but maybe it helps you a little bit:
JsBin: http://jsbin.com/iyulaj/1/
Using a directive you can take the data of the input, parse it and then put it to the $scope variable. Please see: http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController ($parsers and $formatters. I took it from: Using angularjs filter in input element).
The directive could search for a comma and replace it with a dot. It could look like this:
angular.module('MyModule').directive('numberinput', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModelController) {
ngModelController.$parsers.push(function(data) {
//convert data from view format to model format
return data.replace(',', "."); //converted
});
ngModelController.$formatters.push(function(data) {
//convert data from model format to view format
return data.replace('.', ","); //converted
});
// http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController
}
};
});
The input field:
<input ng-model='value' type='text' numberinput='' />
Also note that the input is now a text field. Have a look at this: http://jsbin.com/iyurol/1/
The input is a number field. But type 11,5 and it will output ùndefined (note that I live in Germany and we're using the comma-notation). So the browser doesn't 'parse' the number correctly if it's not in dot-notation, even if you live in regions with comma-notation...
Please correct me, if there's something wrong about that ;)
It could be not related to angular. IMHO that should be provided by the browser. It should be driven by the lang attribute:
<!DOCTYPE html>
<html lang="it">
...
See more details at HTML5 number inputs – Comma and period as decimal marks
As type of the data to be formatted usually is a number shouldn't we convert the number to a string before calling replace()?
I got an error when model data has a numeric value.
The following works better:
ngModelController.$formatters.push(function(data) {
//convert data from model format to view format
return (''+data).replace('.', ","); //converted
});

Getting the last element from a JSON array in a Handlebars template

So, I found that array elements can be accessed in Handlebars using:
{{myArray.2.nestedObject}} and {{myArray.0.nestedObject}}
..to get the third and first elements for instance. (handlebars-access-array-item)
Is there a way to get the last element from an array?
I tried creating a helper for it:
Handlebars.registerHelper("lastElement", function(array) {
return array.last(); //Array.prototype extension
});
...and calling it as follows in the template:
{{lastElement myArray}} or even {{lastElement myArray.lastElement nestedArray}}
Sadly, this doesn't work. Helper functions return strings apparently. What I need is a way to be able to do this even with multi-dimensional arrays.
Should work, I've tested it.
Template:
{{last foo}}
Data:
{foo : [1,2,3,4,5,6]}
Helper:
Handlebars.registerHelper("last", function(array) {
return array[array.length-1];
});
The above piece of code works fine in all the cases. But if the array passed if a null array, possibility of the handlebar function throwing error is there. Instead perform a null check and then return the value accordingly.

JSON error on submit in IE7 (and only IE7): "Cannot deserialize. The data does not correspond to valid JSON."

This is strictly an IE7 problem; IE8 doesn't mind, and neither does FF. Our form throws the following exception when submitting:
Sys.ArgumentException: Cannot deserialize. The data does not correspond to valid JSON.
Parameter name: data
Form header (edited for secrecy; we have a paranoid NDA):
<%using (Ajax.BeginForm("...", "...", FormMethod.Post,
new AjaxOptions {
OnComplete = "OnSaveEditCommunitySuccess",
OnBegin = "OnBegin" },
new { id = "form_Edit...", name = "form_Edit..." }))
I temporarily attached a handler to OnBegin, and looked at the json object that gets passed around. I can guarantee json.get_request().get_body() is identical to what happens in FF, and properly web encoded:
ProfileTabModel.IsEdit=true&ProfileTabModel.HandEnterCommunity=true&ProfileTabModel.CommunityId=26&ProfileTabModel.County=&ProfileTabModel.OrgId=7395& (...)
It might be relevant that this is a large form, and the data sent is at least 2500 bytes. I realize it shouldn't matter in a POSt, and even if it would, it has nothing to do with the reported error, but best mention it, since other forms written on the same pattern have no trouble posting their data.
[EDIT:] When running on debug, IE7 doesn't enter the Action in the controller, while everything else does. Forgot to mention this.
Solution and summary:
The problem occurs when it's time to encode the submit button. Our buttons are generated as <button ... ><span> Submit</span></button>; when it's time to build the request, all other browsers, IE8 included, are smart enough to take only "Submit". IE7 sends all inner HTML. We've had this error in other contexts, but this time I got set on a false path by the JSON error.
I cannot and will not change from <% Http.Button %> to an input; it's beyond me how this could be even considered a viable solution; that extension is used for very good reasons, and if ditching it was an option I wouldn't have asked this question in the first place.
The solution was to add a new filter, extending RequestValidator, and check the request for the offending span tag; if found, replace with the text.
public class HttpRequestValidator : RequestValidator
{
protected override bool IsValidRequestString(
HttpContext context,
string value,
RequestValidationSource requestValidationSource,
string collectionKey,
out int validationFailureIndex)
{
// Check if the Request.QueryString contains a post value of "<span>.....</span>"
if (requestValidationSource == RequestValidationSource.Form
&& !string.IsNullOrEmpty(value) && value.StartsWith("<span>",
StringComparison.InvariantCultureIgnoreCase))
{
var match = Regex.Match(value, #"\A<span>(.*?)</span>\Z",
RegexOptions.IgnoreCase);
if (match.Success && match.Groups.Count > 1)
return base.IsValidRequestString(
context, match.Groups[1].Value,
requestValidationSource,
collectionKey,
out validationFailureIndex);
}
return base.IsValidRequestString(
context,
value,
requestValidationSource,
collectionKey,
out validationFailureIndex);
}
}
You'll also need to add it in web.config; search for httpRuntime and replace it with
<httpRuntime requestPathInvalidCharacters="<,>,*,%,:,\"
requestValidationType=" ... .HttpRequestValidator"
maxRequestLength="40920" maxQueryStringLength="10000"/>
You need to copy/paste your json code into http://jsonlint.com/ to check its validity and see the problem.
Perhaps you'd better to use a normal "input" tag with button type, since it's inside a form. Apparently IE 7 takes the button's inner html as its value, while many other browsers use the "value" attribute.