AngularJS custom directive within ng-repeat with dynamic attributes and two way binding - angularjs

I'm banging my head on the wall over this for days and finally decided to post this question since I can't find an answer that matches what I'm trying to do.
Context: I'm building a dynamic form building platform that describes form elements in a JSON structure like this -
{
"name": "email",
"type": "email",
"text": "Your Email",
"model": "user.profile.email"
}
And then in the View I have a recursive ng-repeat that includes the field template like this -
<script type="text/ng-template" id="field.html">
<div ng-if="field.type === 'email'" class="{{field.class}}">
<p translate="{{field.text}}"></p>
<input type="{{field.type}}" name="{{field.name}}" class="form-control" dyn-model="{{field.model}}">
</div>
</script>
As you see, I use a custom directive dynModel to create the ng-model attribute with interpolated value of the model from the string value. So far do good.
Now I have a more complex scenario in which I have a collection of fields that can be added or removed by clicking on Add button or removeMe button. See below -
{
"name": "urls",
"type": "collection",
"text": "Your Profile URLs",
"model": "user.profile.urls",
"items": [
{
"name": "url",
"type": "url",
"text": "Facebook URL",
"model": "url"
},
{
"name": "url",
"type": "url",
"text": "Facebook URL",
"model": "url"
}
],
"action_button": {
"name": "add",
"type": "action",
"action": "addURL"
}
}
<div ng-if="field.type === 'collection'">
<button class="btn btn-info" dyn-click click-action="{{field.action_button.action}}" click-model="{{field.model}}">{{field.action_button.text}}</button>
<div dyn-ng-repeat="item in {{field.model}}" >
<div ng-repeat="field in field.items" ng-include src="'field.html'"></div>
</div>
</div>
As you'll notice, I have another custom directive that takes care of interpolation of {{field.model}} from the previous ng-repeat (not shown).
Now to the crux of the issue. As you see in the template, I have nested ng-repeats, the first one iterates through user.profile.urls and the second one iterates through the field parameters in JSON and creates the HTML tags, etc. One of those fields is a button (action_button) that is used to add more URLS to the list. When I click the button, I want it to trigger a function in my controller and effectively add a new child to the parent model (user.profile.urls). I then also want each URL, existing and new to have a remove button next to them that will be dynamic and will remove that particular item from the model.
If you see the code above, I have a custom directive dyn-click that reads in the
click-action="{{field.action_button.action}}"
That contains the function name (addURL) to be called that resides in my controller and the model
click-model="{{field.model}}"
(user.profile.urls) to which the new item is to be added. This is not working. The reason for this complexity is that I have multiple levels of nesting and at each level there are dynamic elements that need to be interpolated and bound. The directive dyn-click looks like this right now -
exports = module.exports = function (ngModule) {
ngModule.directive("dynClick",function() {
return {
restrict: 'A',
link: function(scope,element,attrs) {
$(element).click(function(e, rowid){
scope.clickAction(scope.clickModel, scope.$index);
});
}
};
});
};
With this code, when I click on the rendered form's Add button, the code in the $(element).click method above gets executed giving the following error -
Uncaught TypeError: undefined is not a function
I have tried a few different things with scope:{} in the dyn-click directive, with different errors and none of them have worked completely with two way binding of the model and calling the function as expected.
Help!
EDIT-1 - please see the comments:
$(element).click(function(e, rowid){
scope.$eval(attrs["clickAction"])(scope.$eval(attrs["clickModel"]), scope.$index);
});
EDIT-2: The plunker is here - http://plnkr.co/edit/DoacjRnO61g4IYodPwWu?p=preview. Still tweaking it to get it right, but you guys should be able to see the necessary pieces. Thanks!
EDIT-3: Thanks Sebastian. The new plunker is here - http://plnkr.co/edit/Z6ViT7scubMxa17SFgtx?p=preview . The issue with the field.items ng-repeat still exists. For some reason the inner ng-repeat is not being executed. Any ideas? Josep, Sebastian?

Related

Angular // ng-select Use interpolation within a textarea on a text pulled from an array

Still in my very first days with Angular and ran into a problem i can't get my head around.
What I am trying to accomplish is sort of creating a way to have a persons name added to a message template.
The templates are loaded from a file like this
this.http.get('../../assets/templatefiles/customtemplates.json').subscribe(data => {this.templateArray = data as any [];
The structure of the JSON file is as follows
[{
"Id": 1,
"Type": "SR Templates",
"Name": "Message 1",
"Body": "Some meaningful text here"
},
{
"Id": 2,
"Type": "SR Templates",
"Name": "Message 2",
"Body": "Some more meaningful text here"
},
{
"Id": 3,
"Type": "GTFO Templates",
"Name": "Message 3",
"Body": "Guess what? Exactly, some even more..blahhh"
}]
All good so far. Then, in my template I use ng-select to create the dropdown list to display the options grouped by Type
<ng-select [items]="templateArray"
bindLabel="Name"
bindValue="Body"
groupBy="Type"
[(ngModel)]="selectedTemplate"
>
<ng-template ng-optgroup-tmp let-item="item">
<strong>{{item.Id}}</strong>
</ng-template>
</ng-select>
So far...working it seems. Templates are grouped by Type and show up in the drop down just fine.
Below the selection is a textarea in which the "Body" value is supposed to be displayed. Works fine as well, when selecting a template from the drop down the text shows fine in the textarea.
The problem I am facing is that there is an input field for the persons name the message will be send to.
I get the name as follows:
<input type="text" [(ngModel)]="srcName" class="form-control" placeholder="Name">
The bit confusing me is how to get/add the persons name to the message using interpolation?
I was hoping for something like just having to change the text in the JSON and adding the interpolation to it but apparently that does not work hehe.
{
"Id": 1,
"Type": "SR Templates",
"Name": "Message 1",
"Body": "Dear {{srcName}, Some meaningful text here"
}
I've been searching up and down, but am ultimately stuck and am desperate for a nudge in the direction I would have to go to actually get that name inserted in the textarea together with the template from the array...
You can make a function that concatenates your selected template and the name in you input, like so:
onSubmit() {
this.message = `Dear ${this.srcName}, ${this.selectedTemplate}`;
}
And then you can add a button that executes this function:
<button (click)="onSubmit()">Submit</button>
Now whenever you select the template you want and add the name you want to the input and click on submit button, you will get a concatenated message with the info you want.
Here is a live demo if you want more explanation.
In the live demo a used a normal HTML select, but it should work the same with angular material select.
##EDIT
If you need to put your srcName inside of your templates body, you will have to create a place holder in your template's body, something like:
"Guess what? Exactly {srcName}, some even more..blahhh"
instead of:
"Guess what? Exactly, some even more..blahhh"
Then you will have to change the onSubmit function:
onSubmit() {
this.message = this.selectedTemplate.replace('{srcName}', this.srcName)
}
I also added the changes to to the live demo.

ment.io - model is not updating on mouse

I am using ment.io plugin from http://jeff-collins.github.io/ment.io/#/examples with tinyMce for editor support.
All was well until i found that when i select menu item by using mouse, model is not updating automatically, though in editor it is showing selected text properly.
Further while investigation, found that model is updating when we do some key event inside editor after selection with mouse.
While select with arrow key and select using enter or tab, model is updating properly. This might be because, this is the key event which editor seeks in earlier case.
Here is link of fiddle for the scrnario https://jsfiddle.net/vikasnale/2p6xcssf/5/
<div ng-app="App">
<script type="text/ng-template" id="/tag-mentions.tpl">
<ul class="list-group user-search">
<li mentio-menu-item="Tag" ng-repeat="Tag in items" class="list-group-item">
<span class="text-primary" ng-bind-html="Tag.name | mentioHighlight:typedTerm:'menu-highlighted' | unsafe"></span>
</li>
</ul>
</script>
<textarea mentio-id="'tinyMceTextArea'" ui-tinymce="tinyMceOptions" mentio mentio-typed-text="typedTerm" mentio-require-leading-space="true" ng-model="Content" mentio-iframe-element="iframeElement"></textarea>
<mentio-menu id="hastmenu" mentio-for="'tinyMceTextArea'" mentio-trigger-char="'#'" mentio-items="tags" mentio-template-url="/tag-mentions.tpl" mentio-search="searchTags(term)" mentio-select="getTagTextRaw(item)"></mentio-menu>
<br/>
<p>Output Model: {{Content}}</p>
angular.module('App', ['mentio', 'ui.tinymce'])
.controller("Ctrl", ['$scope', 'mentioUtil',
function($scope, mentioUtil) {
$scope.getTagTextRaw = function(item) {
return '<i class="mention-tag-text" style="color:#a52a2a;">' + item.name + '</i>';
};
$scope.searchTags = function(term) {
var tagsList = [];
angular.forEach($scope.allTagList, function(item) {
if (item.id.toUpperCase().indexOf(term.toUpperCase()) >= 0) {
if (tagsList.length <= 5) {
tagsList.push(item);
}
}
});
$scope.tags = tagsList;
return tagsList;
};
$scope.allTagList = [{
"id": "ctp",
"name": "#ctp"
}, {
"id": "earningRelease",
"name": "#earningRelease"
}, {
"id": "presssrelease",
"name": "#presssrelease"
}, {
"id": "inversor-conference",
"name": "#inversor-conference"
}, {
"id": "live release",
"name": "#IACLive"
}, {
"id": "reval",
"name": "#reval"
}, {
"id": "margin",
"name": "#margin"
}, {
"id": "phonecall",
"name": "#phonecall"
}, {
"id": "Q4",
"name": "#Q4"
}];
$scope.tinyMceOptions = {
init_instance_callback: function(editor) {
$scope.iframeElement = editor.iframeElement;
},
resize: false,
width: '100%',
height: 150,
plugins: 'print textcolor',
toolbar: "bold italic underline strikethrough| undo redo",
toolbar_items_size: 'small',
menubar: false,
statusbar: false
};
}
]);
Note : This behavior is observed while using ment.io with tinymce
can't figure out the fix for this..
Please advice...
I faced the same problem and on search came across this post. Since there was no solution I thought I would dig deeper. Here are my findings, If you can add on top of this to find a solution it would be of great help.
(1) I am using MEAN framework with angularJS. And trying to implementMent.io with Tiny MCE
(2) Question: Why is it working with the codepen Ment.io example and not working with the implementation in jsfiddle ..
Answer or observation,
In the codepen implementation if you see they have included the tinymce but have never used it with the div.
also they have implemented a directive with listeners that is applied with ment.io called "contenteditable" this helps in properly replacing the values..
In the Jsfiddle example by Vikas Nale. The text area includes the tinymce editor. So as soon as we apply the Tinymce editor the model stops updating on enter key or mouse click, a space bar has to be pressed inorder for the model to be properly updates.
(3) probable reason Now I thought I will also add the contenteditable directive and it will take care of the events. But it seems that when we apply the tinymce editor, the mention menu, text area etc is placed in an iframe element. Because of which the events are not propagating correctly.
I also tried the Setup: option of tinymce. but as soon as we start that the # menu of Ment.io stops working.
this is as far as I could understand. I need to implement this in a project so any discussion, hint, pointers are welcome.
The above issue of not getting up to date text from the assigned data model was solved for me by getting the data from active editor of tinymce instead of the data model assigned to ment.io text area (see the attached screen shot).

angular-schema-form: Add custom html to form fields

I have just started to look into angular-schema-form, so this might be something I've missed in the docs or description.
What I am trying to do is to add an icon next to the label of generated form fields and next to the field itself. Like so:
But out of the box angular-schema-form will generate:
I know I can make my own custom field types, but is that the way to go? That would require me to redefine all field types in a custom variant, because I need these two icons and their functionality on all my form fields.
I was hoping there were an easier way to add this functionality to generated html, and an easy way to add functionality (ng-click function) on them.
Edit: After reading through the docs again, I've figured out that I need to define my own custom field type (https://github.com/Textalk/angular-schema-form/blob/development/docs/extending.md)
From what I gather, I need to add the following to my modules config block:
schemaFormDecoratorsProvider.addMapping(
'bootstrapDecorator',
'custominput',
'shared/templates/customInput.tpl.html',
sfBuilderProvider.builders.sfField
);
I have also added the contents of shared/templates/customInput.tpl.html to $templatesCache.
But when I try to render a form, with a schema like
"schema": {
"type": "object",
"properties": {
"firstName": {
"title": "First name",
"type": "string"
},
"lastName": {
"title": "Last name",
"type": "custominput"
},
"age": {
"title": "Age",
"type": "number"
}
}
}
I only see the first field (firstName) and age. The custom type is just ignored.
I have tried to debug my way to the problem, but as far as I can see, the custom field is correctly added to the decorator. I've tried to console.log the schemaFormDecoratorsProvider.decorator() and there I can see my custom field type.
I've also tried to fire off a $scope.$broadcast('schemaFormRedraw') in my controller, but I still only see the built in field types.
As a test, I've tried to define my own decorator, overwriting the default Bootstrap decorator:
schemaFormDecoratorsProvider.defineDecorator('bootstrapDecorator', {
'customType': {template: 'shared/templates/customInput.tpl.html', builder: sfBuilderProvider.stdBuilders},
// The default is special, if the builder can't find a match it uses the default template.
'default': {template: 'shared/templates/customInput.tpl.html', builder: sfBuilderProvider.stdBuilders},
}, []);
I would expect to see all fields to be rendered the same, since I only define a default type and my own custom type. But still, I only see built in types rendered, my custominput is till just ignored.
What am I missing?
I've had this same problem, the problem is that you should not confuse the JSON schema with the form definition.
To render a custom component you have to change the form definition. I.e in your controller your standard form defintion might look something like:
$scope.form = [
"*",
{
type: "submit",
title: "Save"
}
];
You'll have to change this to:
$scope.form = [
"firstName",
"age",
{
key:"lastName",
type:"customInput"
},
{
type: "submit",
title: "Save"
}
];

capturing the form data inside the Angularjs controller

I am new to Angularjs. I am having the form data which I need to make a post request to the server passing the data. I have done the UI and controller part inside the angularjs but do not know how to capture the form data. Please find the link to plnkr - Plnkr link
By clicking the add button, a new li element gets added and the same gets deleted when the minus sign is clicked. I need to get all the key value items into the below format for sending for Post request.
{
"search_params": [
{
"key": "search string",
"predicate": "matches",
"value": "choosen text"
},
{
"key": "search string",
"predicate": "not-matches",
"value": " search value"
},
{
"key": "search string",
"predicate": "matches",
"value": " search value"
}
]
}
How to capture the form data and construct the param object inside my controller inside the searchParams function inside the controller. Please let me know as I am new to Angularjs.
Updated Question
Based on the inputs, I am able to get the user details in the controller. But there are few things:
By default there will be one li element and when the user submits, the current li elements data should be captured in the controller.
Only when I add the criteria using the plus button, the array is getting updated, but the last elements data is not being updated in the model when submitted.
The same is holding good for the deleting the criteria too.
Link to updated Plunker - Plnkr Link
Expanding on #Chris Hermut and assuming you want an array of map, according to json you posted. You can do that by
var arr = [];
var form = {
name: 'asd',
surname: 'aasdasdsd',
wharever: 'asd'
}
arr.push(form);
//prentending to be another (key,value)
form.surname = 'hfg';
arr.push(form);
here's a fiddle illustrating just that.
Directives like <select>, <input> require ng-model attribute to correctly bind your input to $scope properties.
In your HTML markup you'll have to update your form elements with required attributes (like ng-model).
I would recommend (in controller/link) to use only one object for form data with different properties like
var form = {
name: '',
surname: '',
wharever: ''
}
Corresponding <input> would be ex. <input ng-model="form.name" type="text">
After you have your 'form' object populated you can do JSON.stringify(form) before your request (if your using some other content-type then application/json).

Angular JS: Handling different UI components from each views repeated using ng-repeat when the view look-feel are same

Apologies for the big Title, I couldn't come up with any thing better. Let me explain the issue I am having.
I have to render three cards where each of them share same look-feel, means each of them have a header section and a body section. I am using ng-repeat to get data from model and render these cards.
Code:
<div class="card">
<h1>{{card.title}}</h1>
<div ng-repeat="widget in card.widgets" class="widget">
<h2>{{widget.title}}</h2>
{{widget.type}}
</div>
</div>
Now, each of these Card's body should have different UI in it. For example, One card's body might have a chart using Hight Charts, Another one might just want to use a UI from jQuery UI library, etc..
How would I achieve it when I am looping using ng-repeat? Let me know if my starting direction is correct?
The model data look like this:
[
{
"id": "c001",
"title": "CARD 1",
"widgets": [
{
"title": "title 1.1",
"type": "line-graph"
},
{
"title": "title 1.2",
"type": "bar-chart"
}
]
},
{
"id": "c002",
"title": "CARD 2",
"widgets": [
{
"title": "title 2.1",
"type": "graph"
},
{
"title": "title 2.2",
"type": "bar-chart"
}
]
},
{
"id": "c003",
"title": "CARD 3",
"widgets": [
{
"title": "title 3.1",
"type": "line-graph"
},
{
"title": "title 3.1",
"type": "bar-chart"
}
]
}
]
Looking for help on this.
Thank You.
The solution for your problem is to combine both ng-switch and ng-repeat to achieve the effect you are attempting to get. Basically you would repeat over the items and switch between its type. Angular only includes the part of the DOM which matches the switch.
Here is a rough idea / html of what you should be doing.
<div class="card">
<h1>{{card.title}}</h1>
<div ng-repeat="widget in card.widgets" class="widget">
<h2>{{widget.title}}</h2>
<div ng-switch on="widget.type">
<div ng-switch-when="line-graph">
<!--do something here relevant to line graph-->
<div line-graph="widget.data"></div>
</div>
<div ng-switch-when="graph">
<!--do something here relevant to graph-->
<div graph="widget.data"></div>
</div>
<!-- and so on... add more if you need-->
</div>
</div>
</div>
I'm newer to Angular, but I believe you could encapsulate all of it through a directive. The idea would be we would have our own directive that we add to a div and then pass in the type and from there the directive could handle the logic and create the appropriate chart/element/whatever and do all the creation in the javascript file for the directive.
<div chart="line-graph"></div>
<div chart="bar-chart"></div>
module.directive('chart', function(){
return{
//logic to build various charts with different libraries here
}
});
It might be a pretty complicated directive to write, but it would be an elegant way to write it. #ganaraj did have that above with div graph="widget.data" inside of the ng-switch, but didn't really mention the directive part, just the switching. That would make the individual directives simpler, so may be the better overall approach, if each type is going to be vastly different.
This post below from simpulton is really good covering the directives portion of it. You can even make it a step further and make it more like a widget where the tag could be (his "act five: widget directive") and then to that pass in the type and the data to go with it. He is writing a directive to do animantion on some cirlces created in CSS, so there's no reason you couldn't use it to apply some highchart or jQuery UI code. It's really well written and includes code in jsfiddle so you can see it work as well.
http://onehungrymind.com/angularjs-directives-basics/

Resources