AngularJS: Writing to and Reading from textarea with multilines - html

I can't believe why I can't find anything to this topic ...
I got a form with let's say lastname (input), firstname (input), description (textarea as I want provide several lines). Let's start with the creation of a new object:
Okay, you type something in like
lastname: fox
firstname: peter
description:
what can I say ..
well I'm THE guy
bye
This arrives at my Java Spring MVC Backend Controller as what can I say ..\nwell I'm THE guy\n\nbye which is fine as I can determine where line breaks are.
So, now I want to edit this object. Thus I want to read the stored data and put it in the form. On Serverside I now edited the description text so that I replaced the \n with <br> so that I have HTML breaks.
Now I use angular-sanitize (ngSanitize dependency) and use the directive ng-bind-html="my.nice.description"
If I use this on a DIV, everything works fine, HTML gets rendered and I get my breaks. So this works perfectly:
<span ng-bind-html="my.nice.description"></span>
or one of the following:
<div ng-bind-html="my.nice.description"></div>
<p ng-bind-html="my.nice.description"></p>
BUT as I want to (re)fill my form so the user can edit his previous input, I still use a textarea. Like this:
<textarea ng-bind-html="my.nice.description"></textarea>
And this does NOT work in any way. Which means that I get 's in my text, unrendered.
Though this seems like a ridicilous normal task. It's a form with a simple multiline box, so I want to write my several lines and I want to read them and I want to edit them.
As I am rather a backend guy and not very familiar with HTML and AngularJS I hope that I'm just using the wrong html element or something like this ... Maybe someone can help me out? It's frustrating :(
Thanks in advance and I guess and hope this is not a real hard task :x

store 'br' in your model, so you can use ng-bind-html. add a directive to your textarea which makes the conversion between your $viewVale ('\n') and your model.
.directive('lbBr', function () {
return {
restrict: 'A',
require: '?ngModel',
link: function (scope, element, attrs, ngModel) {
if (!ngModel) {
return;
}
ngModel.$parsers.unshift(function(value) {
return value.replace(new RegExp('\n', 'g'), '<br />');
});
ngModel.$formatters.unshift(function(value) {
if (value) {
return value.replace(new RegExp('<br />', 'g'), '\n');
}
return undefined;
});
}
};
});

<textarea> elements cannot contain other elements (in your case, <br>'s). They are standalone. You'll have to convert the variable-returned-from-server-containing-<br>'s back to \n's, and vice versa back and forth. You can use an angular directive that handles that for you.

Related

How would you embed atomic partials into a template data object

I'm using handlebars and assemble with yeoman and gulp.
I want to have some globalized partials that are able to be nested or injected into another partial by calling it within the context of a data object.
A simple example of that would be having a list of links that I could reference inside content throughout the site. The reason behind this, is the need for consistency. If for example, if I have a link within text on a page that I reference a 15 times throughout an entire website, but then realize I need to add a trade mark or modify the text, I want to update it once, not 15 times.
This is an example of what I want to do. Define global data inside a json file:
links.json
{
"apple": {
"linktext": "apple",
"target": "_blank",
"href": "http://www.apple.com"
},
"blog-article-foo-bar": {
"linktext": "foo bar",
"href": "http://www.foobar.com"
},
"dell": {
"linktext": "dell",
"target": "_parent",
"href": "http://www.dell.com"
}
}
Generate a partial from that content using a simple or complex template:
links.hbs
<a href="{{href}}" {{#if target}}target="{{target}}"{{/target}}>{{linktext}}</a>
And be able to embed that partial into another one by referencing it some how. This didn't work, but I've been reading about custom helpers, but can't figure out how I would intercept the partial and bind it into the other partial.
text.json
{
"text": "If you need a computer, go to {{> link link.apple}}."
}
text.hbs
<p>
{{text}}
</p>
compiled.html
<p>
If you need a computer, go to apple.
</p>
If you have suggestions or examples that might help me understand how to achieve this, I'd really appreciate the support. Thanks in advance.
There is some information about Handlebars helpers in their docs but not that much.
Since you're trying to use handlebars syntax in the value of a property on the context (e.g. text), handlebars won't render the value since it's already rendering the template. You can create your own helper that can render the value like this:
Handlebars.registerHelper('render', function(template, options) {
// first compile the template
const fn = Handlebars.compile(template);
// render the compiled template passing the current context (this) to
// ensure the same context is use
const str = fn(this);
// SafeString is used to allow HTML to be returned without escaping it
return new Handlebars.SafeString(str);
});
Then you would use the helper in your templates like this:
{{render text}}
Thanks for the example #doowb, your code did work but not for what I was trying to do. I really wanted something more complicated but I simplified my question not knowing it would be an issue. The code you provided worked (I think after a slight tweak) for a simple render of a template, but my templates use helpers such as #each and #if which caused the issue. Where the helpers were in my template, I ended up getting async placeholders. For example: <a $ASYNC$1$3...> I later learned this has to do with how partials are rendered. Understanding that lead me to subexpressions and the below solution.
Keeping my example above with some modifications, this is how I was able to merge partials.
First, I simplified the placeholder in text.json to basically a unique ID, instead of trying to render the partial there.
On the hbs template that I'm rendering to, such as a page or whatever, I included the insert helper with 3 arguments. The first two are subexpressions, each return a flattened partials as strings. The key here is that subexpressions process and return a result before finishing the current process with the helper. So two flattened templates are then sent to the helper along with the placeholder to search for.
The helper uses the third argument in a regex pattern. It searches the second argument (flattened parent template) for this pattern. When found, it replaces each instance of the pattern with the first argument (yes its a global fine replace).
So, the flattened child string gets inserted into parent each time placeholder is found.
First argument
(partial "link" link.apple)
Returns
'apple'
Second argument
(partial "text" text.text-example)
Returns
'<p class="text font--variant">If you need a computer, go to {{linkToApple}}.</p>'
Third argument
'linkToApple'
text.json
{
"text-example": {
"elm": "quote",
"classes": [
"text",
"font--variant"
],
"text": "If you need a computer, go to {{linkToApple}}."
}
}
text.hbs
<{{elm}} class="{{#eachIndex classes}}{{#isnt index 0}} {{/isnt}}{{item}}{{/eachIndex}}">{{text}}</{{elm}}>
compile.hbs
{{insert (partial "link" link.apple) (partial "text" text) 'linkToApple' }}
compile.html
<p class="text font--variant">If you need a computer, go to apple.</p>
gulpfile.js
app.helper('insert', function(child, parent, name) {
const merged = parent.replace(new RegExp('\{\{(?:\\s+)?(' + name + ')(?:\\s+)?\}\}', 'g'), child);
const html = new handlebars.SafeString(merged);
return html;
});
Hope this helps someone else. I know this can use improvements, I'll try to update it when I get back to cleaning up my gulp file.

Binding dynamically within an ng-repeat expression

For a TV Guide, I am trying to create a dynamic expression within an ng-repeat directive as follows:
<div ng-repeat="programme in programmes['{{channel}}-wed-jan-14']" alt="{{channel}}">
{{channel}} in my controller should evaluate to something like "eTV". The binding is working fine with the alt="{{channel}}" instance but not with the array instance. Angular simply serves up the line of code commented out. If I hardcode the "eTV" string in place of the {{channel}}, it works fine.
Am I trying to ask Angular to do what it is not designed for, or is it possibly my array handling which is dodgy?
Okay, not sure if I just asked a dumb question, but in the absence of responses, I managed to figure out a solution by writing a filter as follows:
Template:
<div ng-repeat="programme in programmes | getChannelDay:channel:dayString" alt="{{channel}}">
Controller filter:
app.filter('getChannelDay', function() {
return function(programmes, channel, dayString) {
return programmes[channel + dayString];
};
});
The issue with my initial problem
<div ng-repeat="programme in programmes['{{channel}}-wed-jan-14']" alt="{{channel}}">
is that I was trying to put {{channel}} inside the expression, but that is the format for markup.
I tried to use the following instead:
<div ng-repeat="programme in programmes['channel + daystring']" alt="{{channel}}">
but I am doing something wrong here. I am pretty sure there is a way to get this to work - if anyone knows, please comment.

Returning functional HTML code through Angular JS filter

I'm trying to filter any text through angular to return a font-awesome icon. Instead It's just returning plain text. Anyone know a fix? Thanks!
.filter('textToIcon', function($sce) {
return function(text) {
if(text!==''){
return '<i class="fa fa-child"></i>';
}
}
This is "Strict Conceptual Escaping" (SCE) in action.
For more information on what it is and how it works, take a look at this answer.
In a few words, Angular tries to protect your users from untrusted content that could do bad things to them. If you know that the values are safe to be interpreted as HTML, there are two approaches you can take:
Import ngSanitize (it is available as a separate module) and add it as a dependency in your module (e.g. angular.module('myApp', ['ngSanitize'])). You are set (as long as you pass safe values to ngBindHtml) !
Use $sce to specify that certain stuff is explicitly trusted (by you) to be used as HTML:
.filter('textToIcon', function ($sce) {
return function (text) {
if (text) {
return $sce.trustAsHtml('');
}
};
})
The former approach is strongly recommended !

Load html conent after loading page?

I have working with one angularjs example i have face one problem is that i have load html view then after one div is content html data that is come from controler(database call) also data content html tag like <hr> <images> but that are display as it is not render html so i want to render that html part to.
I know my problem is delayed data come from data base so that will dispay as a plan text.
I use ng-bind-html till they are display pain text not render html tags.
I have one answer is late page loading that will succesfully work but that is not the proper way bcoz some time database data may take long time that is not working in this type of condition.
Hi jay you have to make directive for your example which is name bind-unsafe-html pass your html string or content in that and then it will re render your html content.
For Example.
app.directive('bindUnsafeHtml', ['$compile', function ($compile) {
return function(scope, element, attrs) {
console.log("in directive");
scope.$watch(
function(scope) {
// watch the 'bindUnsafeHtml' expression for changes
return scope.$eval(attrs.bindUnsafeHtml);
},
function(value) {
// when the 'bindUnsafeHtml' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
}]);
I can understand your problem it's a problem of asynchronous jscript call.
http://www.w3schools.com/tags/att_script_async.asp
you can not expect the script embedded in your HTML code to work properly.
You need to separate it from your HTML and make it async.

in igCombo - How to display in the combo's input the selectedItem's tepmlate

I have an igCombo in durandal project. I load the igCombo through the date-bind property at the dom. I created an itemTemplate for the select element options. I want that where I select any item, the combo's input will show the selectedItem template. Here is my code, but it doesn't work well; it shows in the inpute the follow thing:
[object object]
here is my code:
<span id="combo" data-bind="igCombo: { dataSource: data, textKey: 'name',
valueKey: 'id', width: '400px',
itemTemplate: '${name} | ${id}',
allowCustomValue: true,
selectionChanged: function (evt, ui) {
var concatenatedValue = ui.items.template
ui.owner.text(concatenatedValue);}
}">
</span>
(Please don't answer me that I can simply write in the selectionChanged function the sane piece of code that I wrote in the itemTemplate property, becouse now it is small piece of code, but when it will be longer code- it is not nice to write it twice!!!)
can you help me?
I could try to explain why the combo input would not intentionally use the itemTemplate - the template is meant to be mostly rich HTML content (images, links and whatnot as in this sample http://www.infragistics.com/products/jquery/sample/combo-box/templating) and you can't put that in an input field.
However, in your case you are just using text so it is doable - first the ui.items provided to the event (as the name suggests) is a collection, so take the first one and the items don't have template property unless that is part of your model that I can't see.
Like other Ignite UI controls, the Combo uses the Templating Engine and so can you! Take the itemTemplate from the control and the item from the data source like in this snippet:
function (evt, ui) {
var templatedValue = $.ig.tmpl(ui.owner.options.itemTemplate, ui.owner.options.dataSource[ui.items[0].index]);
ui.owner.text(templatedValue);
}
JSFiddle: http://jsfiddle.net/damyanpetev/tB7Ds/
The templating API is much like the old jQUery templating if you are familiar with that - taking a template and then data object.Using the values from the control itself means you can make them as complicated as you want and write them in one place only, this code doesn't need to change at all.