I am working on a angular js project. Right now the controls are static. But client wants to create the html controls based on database.
I have a table with controls specification
For eg:
type : text/dropdown/radio/checkbox
events : onchange/onblur/onfocus
attributes:"color:red"
How can I generate the dynamic model which can parse database value to html controls?
Any help would be really appreciated...
This is pretty easy using the ng-repeat directive. Note how I assign the value of the model back to the scope variable in the ng-repeat. This allows me to retrieve it later.
angular.module('formModule', []).
controller('DynamicFormController', ['$scope',
function($scope) {
//Set equal to json from database
$scope.formControls = [{
name: "Name",
type: "text"
}, {
name: "Age",
type: "number",
color: "red"
},
{
name: "UseNestedControls",
type: "checkbox",
nestCondition: true,
nestedControls: [{
name: "NestedText",
type: "text"
}]
}
];
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="formModule">
<div ng-controller="DynamicFormController">
<form>
<div ng-repeat="input in formControls">
<label>{{input.name}}</label>
<input type="{{input.type}}" ng-model="input.value" ng-style="{'color':input.color}" />
<div ng-repeat="nestedInput in input.nestedControls" style="margin-left:20px" ng-show="input.value == input.nestCondition">
<label>{{nestedInput.name}}</label>
<input type="{{nestedInput.type}}" ng-model="nestedInput.value" ng-style="{'color':nestedInput.color}"/>
</div>
</div>
</form>
<div ng-repeat="input in formControls">
<span>{{input.name}} = {{input.value}}</span>
<div ng-repeat="nestedInput in input.nestedControls" style="margin-left:20px">
<span>{{nestedInput.name}} = {{nestedInput.value}}</span>
</div>
</div>
</div>
</body>
Related
hopefully someone there who can guide me on rite way. trying to create dynamic html with dynamic control. here is my example.
const [oHtml, setoHtml] = React.useState([
{
type: "html",
control: "<table><tr><td>",
},
{
type: "control",
control: (
<CustomInput
labelText="Password"
id="password"
formControlProps={{
fullWidth: true,
}}
inputProps={{
type: "password",
autoComplete: "off",
}}
/>
),
},
{
type: "html",
control: "</td></tr></table>",
},
]);
and here how i am rendering.
return (
<div>
{Object.keys(oHtml).map(function (keyName, keyIndex) {
if (oHtml[keyName].type === "html") {
console.log(oHtml[keyName].control);
return ReactHtmlParser(oHtml[keyName].control);
} else {
return oHtml[keyName].control;
}
})}
</div>
);
issue is when React run <table><tr><td> its auto fixing tag and making it <table><tr><td></td></tr></table>
what i want to control inside td.
is there any way i can disable react to add missing tags itself?
The thing is that ReactHtmlParser can't be used to parse partial html.
You can try
return ReactHtmlParser(
oHtml.map(v => v.control).join("")
)
Btw, your oHtml is an array, so no need for Object.keys.
I have HTML that is rendered based on loaded metadata. It ends up forming a table of data based on a scoped value in the controller.
In the controller:
$scope.arrayOfCustomObjects = [{ entry: data1 },{ entry: data2 }];
The metadata is contained in a .json file, with the following format (I added two here, in reality there are hundreds, each one basically ends up describing an HTML element):
loadedMetaData:
{
"field_1": {
"index": 0,
"type": "selectbox",
"nameId": "arrayOfCustomObjects" <-- this is the string name of a scoped variable in a controller.
},
"field_2": {
"index": 1,
"type": "textinput",
"nameId": "textField"
}
}
And the HTML looks like this:
<div ng-repeat="field in loadedMetaData>
<div ng-repeat="item in field.nameId">
<!-- build out HTML for each -->
</div>
</div>
When I run this, it doesn't work (it never iterates over $scope.arrayOfCustomObjects). If I add a line to display {{field.nameId}} is displays 'arrayOfCustomObjects' but I think it's just the string, not the value it represents.
If I change the HTML to this it does work:
<div ng-repeat="field in loadedMetaData>
<div ng-repeat="item in arrayOfCustomObjects">
<!-- build out HTML for each -->
</div>
</div>
...but is there any way to keep the abstraction I'm going for? I'd like to be able to define the target array in the metadata so I don't have to define the controller-specific names in the HTML itself.
Suppose that your loadedMetadata is like:
[
"arrayOfCustomObjects",
// other 'data' object keys
]
Then you could have an object like this that stores your actual data:
data = {
"arrayOfCustomObjects": [
// some items
]
}
Then, in the template you do:
<div ng-repeat="field in loadedMetaData>
<div ng-repeat="item in data[field]">
<!-- display the items in arrayOfCustomObjects -->
</div>
</div>
This takes the value as string
var field = { "nameId": "arrayOfCustomObjects" }
change this to
var field = { "nameId": arrayOfCustomObjects }
Use a bracket notation property accessor with the this keyword:
<div ng-repeat="(fieldKey, fieldValue) in loadedMetaData>
<div ng-repeat="item in this[fieldValue.nameId]">
<!-- build out HTML for each -->
</div>
</div>
From the Docs:
It is possible to access the context object [($scope)] using the identifier this and the locals object using the identifier $locals.
— AngularJS Developer Guide - Expression Context
To achieve expected result, use below changes from your plunker code
Using scope function to convert string to scope variable
Rendering that variable inside inner ng-repeat
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.arrayOfCustomObjects = [1,2,3]
$scope.loadedMetaData = {
"field_1": {
"index": 0,
"type": "selectbox",
"nameId": "arrayOfCustomObjects"
},
"field_2": {
"index": 1,
"type": "textinput",
"nameId": "textField"
}
}
$scope.getVariable = function(val){
return $scope[val]
}
});
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="myCtrl as ctrl">
<div>
<div ng-repeat="field in loadedMetaData">
<div ng-repeat="item in getVariable(field.nameId)">
{{item}}
</div>
</div>
</div>
</div>
</body>
</html>
codepen - https://codepen.io/nagasai/pen/XGLZzX
Here is Json schema :
{
"_id" : ObjectId("59031d77fd5e1c0b3c005d15"),
"resume_data" : {
"work_experience" : [
{
"company" : "example",
"website" : "example.com",
"position" : "Internship",
"highlights" : "Learn To Create API In Laravel Framework. and also Learn Angular 2 for Front end Development.",
"project_experience" : [
{
"projectName" : "Fb Project",
"teamMember" : "5",
"technology" : "PHP,Laravel-5,Angular-2,MongoDb",
"projectPosition" : "Back-end Developer"
}
]
}
]
}
}
Here is image:
I have reference of this answer but i don't know about nested form data. can anyone explain how to implement it.
Here is your code, which sets the data you are receiving from backend, here I have stored it in a variable data.
Please notice, this is a shortened version of your form, but the basics are there, you only need to add the few missing properties in corresponding form arrays.
The build of the empty form looks is just a FormArray named work_experience matching your json structure:
this.myForm = this.fb.group({
work_experience: this.fb.array([])
})
We add the fields when you are receiving the data, call a function called setWorkExperience in the callback when receiving data:
setWorkExperience(){
// get the formarray
let control = <FormArray>this.myForm.controls.work_experience;
// iterate the array 'work_experience' from your JSON and push new formgroup with properties and the inner form array
this.data.work_experience.forEach(x => {
// add the rest of your properties also below
control.push(this.fb.group({company: x.company, project_experience: this.setFormArray(x)}))
})
}
setFormArray is called from the previous function, where we patch the data with from project_experience to the inner form array:
setFormArray(x) {
// create local array which is returned with all the values from the 'project_experience' from your JSON
let arr = new FormArray([])
x.project_experience.map(y => {
// add the rest of your properties below
arr.push(this.fb.group({projectName: y.projectName}))
})
return arr;
}
The template would then look like this:
<form [formGroup]="myForm">
<!-- Outmost array iterated -->
<div formArrayName="work_experience">
<div *ngFor="let a of myForm.get('work_experience').controls; let i=index">
<h3>COMPANY {{i+1}}: </h3>
<div formGroupName="{{i}}">
<label>Company Name: </label>
<input formControlName="company" /><span><button (click)="deleteCompany(i)">Delete Company</button></span><br><br>
<!-- inner formarray iterated -->
<div formArrayName="project_experience">
<div *ngFor="let b of myForm.controls.work_experience.controls[i].controls.project_experience.controls; let j=index">
<h4>PROJECT {{j+1}}</h4>
<div formGroupName="{{j}}">
<label>Project Name:</label>
<input formControlName="projectName" /><span><button (click)="deleteProject(a.controls.project_experience, j)">Delete Project</button></span>
</div>
</div>
<button (click)="addNewProject(a.controls.project_experience)">Add new Project</button>
</div>
</div>
</div>
</div>
</form>
In the template you can see the buttons for add and delete of projects and companies. Adding and deleting companies are straightforward, where initCompany() returns a formGroup:
deleteCompany(index) {
let control = <FormArray>this.myForm.controls.work_experience;
control.removeAt(index)
}
addNewCompany() {
let control = <FormArray>this.myForm.controls.work_experience;
control.push(this.initCompany())
}
In the add project we pass as parameter from the template the current formArray control, to which we just push a new FormGroup:
addNewProject(control) {
control.push(this.initProject())
}
In the delete function we pass the current formarray as well as the index of the project we want to delete:
deleteProject(control, index) {
control.removeAt(index)
}
That should cover pretty much everything.
Plunker
Please Check it Out This
Plunker Here
Json Store Like This
{
"name": "",
"work_experience": [
{
"name": "asdasd",
"project_experience": [
{
"Project_Name": "asdasdasd"
},
{
"Project_Name": "asdasdasd"
}
]
}
]
}
I'm trying to create a directive which takes some JSON data, and creates a form. Each of the inputs in the form are contained in a <div> wrapper. The wrapper also contains a <div> that appears when ng-show is used. The <div> using ng-show is for dynamically displaying errors.
HTML for the Directive:
<div class="input-wrapper" ng-repeat="inputData in contactCtrl.formInputData">
<input type="text" class="text-input" placeholder="{{ inputData.placeholder }}" ng-model="inputData.model" />
<div class="error-bubble" ng-show="inputData.showFunction">
</div>
</div>
HTML on the Page:
<div id="column-right">
<h2>
Send me an Email
</h2>
<contact-form></contact-form>
</div>
Directive Creation:
spaModule.directive("contactForm", function() {
return {
restrict: "E",
templateUrl: "partials/directives/contact-form.html"
}
});
JSON Data and a Validation Function:
this.formInputData = [
{
placeholder: "Name",
model: "contactCtrl.clientName",
showFunction: "!contactCtrl.validateName()"
},
{
placeholder: "Email",
model: "contactCtrl.email",
showFunction: "!contactCtrl.validateEmail()"
},
{
placeholder: "Subject",
model: "contactCtrl.subject",
showFunction: "!contactCtrl.validateSubject()"
}
];
this.validateName = function() {
if (this.clientName !== "") {
this.nameError = "Name is required!";
return true;
} else {
return false;
}
};
Not a single piece of this is working. The placeholder is not being rendered correctly, ng-show is not doing anything, and the ng-model is not working. I've tried reformatting this as normal HTML in my page and everything works flawlessly.
The issue appears to be declaring ng attributes with ng-repeat. What am I doing wrong?
So, just getting started in Angular and it's pretty tricky, coming from a pretty simple JS and jQuery background. Here's what I'm trying to do. I have a "tag template" that has a couple categories and then some sub-tags contained within. I have defined these as an object, with the idea that the object/file can be called via file request and manipulated, etc.
I have loaded labels and tag category inputs dynamically by using a factory service and a controller with ng-repeat. Likewise, I have deposited the subtags into another div on page2 (using jQuery mobile page swiping). I'd like to use the checkbox state of the category tags to show/hide the sub-tags on page2.
I have tried dozens of things and searched all over stackexchange, the net, etc, but is simple and straightforward and similar enough for me to get it working. If someone can point me in the right direction, that would be great. Keep in mind that my next step is to add a button on page 1 to add a new category, and buttons on page 2 to add sub-tags to the sub-tag categories.
Finally, I have one more weird thing to report. If I only have two pages in my DOM, I have some weird behavior when loading the page. If I load from page 1, the tag checkboxes do not function, and I see a slight fattening of the border of the labels. If I swipe left to page 2 and reload from this page, the borders of the labels are thin and the checkboxes function. Cannot track down why this would be happening. My hacky workaround is to add an empty page zero and then changepage immediately to page one, but this is far from ideal. Any thoughts on that would be appreciated as well.
Here it is:
HTML
<!-- Angular version -->
<button class="ui-btn" onclick="selectTemplate();">My Template</button>
<form>
<div data-role="controlgroup">
<fieldset data-role="controlgroup">
<div ng-controller="templateCtrl">
<label
class="ui-checkbox"
ng-style="{backgroundColor: '{{tagCat.color | bgColor}}'}"
ng-repeat="tagCat in template"><input type="checkbox"
class="ui-checkbox"
id="{{tagCat.name}}"
ng-model="clicked"
ng-click="click();"
/>{{tagCat.name}}</label>
<div ng-repeat="tagCat in template">{{cb}} {{tagCat.name}} hallo</div>
</div>
</fieldset>
</div>
<div style="display:none" class="flashNotification"></div>
</form>
</div>
<div data-role="page" id="two">
<button class="ui-btn" onclick="selectTemplate();">My Template</button>
<form>
<div data-role="controlgroup">
<div ng-controller="templateCtrl">
<div ng-repeat="tagCat in template" ng-show="clicked" class="{{tagCat.name}}">{{tagCat.name}}
<fieldset data-role="controlgroup">
<label class="ui-checkbox"
ng-repeat="item in tagCat.items"
ng-style="{backgroundColor: '{{tagCat.color | bgColor}}'}"
for="item.name">{{tagCat.color | bgColor}}
<input class="ui-checkbox"
name="{{item.name}}"
id='{{item.name}}'
type="checkbox" />{{item.name}}</label>
</fieldset>
</div>
</div>
</div>
<div style="display:none" class="flashNotification"></div>
</form>
</div>
</div>
JS for jQuery Mobile
$(document).ready(function() {
// addTemplateItems(tagTemplate); // not necessary with Angular
// $.mobile.changePage('#two', { transition: 'none' }); // required or checkboxes don't work on load
$.mobile.changePage('#one', { transition: 'none' });
// // $("[data-role=controlgroup]").controlgroup("refresh");
// set up page nav
$(document).delegate('.ui-page', "swipeleft", function(){
var $nextPage = $(this).next('[data-role="page"]');
var $prevPage = $(this).prev('[data-role="page"]');
console.log("binding to swipe-left on "+$(this).attr('id') );
// swipe using id of next page if exists
if ($nextPage.length > 0) {
$.mobile.changePage($nextPage, { transition: 'slide' });
} else {
var message = 'tagged!';
// save tags here
flashNotify(message);
console.log('fire event!');
$('#flashNotification').promise().done(function () {
$('#group1').hide();
$('#group2').hide();
$('.ui-btn').hide();
// addTemplateItems(tagTemplate);
$.mobile.changePage($prevPage, { transition: 'none' });
captureImage();
});
}
}).delegate('.ui-page', "swiperight", function(){
var $prevPage = $(this).prev('[data-role="page"]');
console.log("binding to swipe-right on "+$(this).attr('id') );
// swipe using id of next page if exists
if ($prevPage .length > 0) {
$.mobile.changePage($prevPage, { transition: 'slide', reverse : true });
} else {
alert('no backy backy!');
}
});
// $("input[type='checkbox']").checkboxradio().checkboxradio("refresh");
});
JS for Angular App
var app = angular.module('STL', []);
app.factory('TagTemplate', [function () {
var TagTemplate = {};
var tagTemplate = {
family: {
name: "family",
description: "These are your family members.",
color: "red",
items: [
{
name: "Joe"
},
{
name: "Mary"
},
{
name: "Jim"
}
]
},
design: {
name: "design",
description: "Different types of design notes.",
color: "blue",
items: [
{
name: "inspiring"
},
{
name: "fail"
},
{
name: "wayfinding"
},
{
name: "graphics"
}
]
},
work: {
name: "work",
description: "Stuff for work.",
color: "green",
items: [
{
name: "whiteboard"
},
{
name: "meeting"
},
{
name: "event"
}
]
}
};
TagTemplate = tagTemplate;
return TagTemplate;
}])
// Controller that passes the app factory
function templateCtrl($scope, TagTemplate) {
$scope.template = TagTemplate;
$scope.click = function(model) {
console.log(this.checked, this.tagCat.name);
}
}
app.filter('bgColor', function () {
return function (color) {
// console.log(color, $.Color(color).lightness(.05).toHexString(.05));
// var rgba = $.Color(color).alpha(.05);
return $.Color(color).lightness(.97).toHexString();
}
})
For the main part, success!
I found a jsfiddle that gave me a good base for experimenting. After some playing, I realized that I just have to create a show property within each of the categories in my data service model, and then assign the ng-model to that property to control it.
I had to do it slightly differently in my own code, but the understanding gained from the following jsfiddle led me to the answer:
http://jsfiddle.net/Y43yP/
HTML
<div ng-app ng-controller="Ctrl">
<div class="control-group" ng-repeat="field in customFields">
<label class="control-label">{{field}}</label>
<div class="controls">
<input type="text" ng-model="person.customfields[field]" />
<label><input type="checkbox" ng-model="person.show[field]" /></label>
</div>
</div>
<button ng-click="collectData()">Collect</button><button ng-click="addField()">Add Field</button><br/><br/>
<em>Booleans</em>
<div ng-repeat="field in customFields">
<p>{{field}}: {{person.show[field]}}</p>
</div>
<em>Show/Hide</em>
<div ng-repeat="field in customFields">
<p ng-show="person.show[field]">{{field}}: {{person.customfields[field]}}</p>
</div>
</div>
JS
function Ctrl($scope) {
$scope.customFields = ["Age", "Weight", "Height"];
$scope.person = {
customfields: {
"Age": 0,
"Weight": 0,
"Height": 0
},
show: {
"Age": false,
"Weight": false,
"Height": false
}
};
$scope.collectData = function () {
console.log($scope.person.customfields, $scope.person.show);
}
$scope.addField = function () {
var newField = prompt('Name your field');
$scope.customFields.push(newField);
}
}
Still having the checkbox issue but I'll open a separate issue for that if I can't figure it out.
Thanks.