Dynamic form in AngularJS - javascript

I'm developing a CMS for a customer, it's all based on AngularJS with its controllers, views, services, etc.
What I need is a pattern where a dynamically loaded script injects some data in an existing scope.
Ok, in human words: I have a form managed by a controller. This form has several preset fields. These fields are managed by an array of the scope, something like:
$scope.fields = [
{ type: "text", name="first_name" },
{ type: "text", name="last_name" },
{ type: "email", name="email" }
];
The view prints dynamically the fields (i.e. it's a scaffolding).
When the customer log into the application I check if in his profile he has a custom script to load, if so the application appends a javascript to the DOM, the javascript file name is equal to the username of the logged user.
So, if the user is called "darko" and he has a custom script enabled, the application append this file to the DOM:
/js/customers/darko.js
Let's say that darko has further fields to show (and save) inside the form, how can I do that? I'd need to hook the controller so I can have access to its scope and then inject my fields. Something like:
var $scope = getUserFormScope();//some magic....
$scope.fields.push({ type: "text", name="skype" });
However, The form with further fields is just an example, what I really need, more generally, is a way to "hook controllers" and have access to theirs scopes.
Any idea?
SOLUTION
I've finally used the method suggested by marfarma. The custom script contains one or more partial controllers named with the same name of the controller they want to extend prefixed by Custom word, then I extend my controllers with these partial controllers. For example, my app has a controller named PageController, inside this controller I check if a CustomPageController exists:
if (typeof CustomPageController == 'function') {
angular.extend(this, CustomPageController($scope));
}
if so, I extend the main controller with the custom one.

Here is a general way to "hook controllers" and have access to their scopes - mixin your hook code via angular.extend.
function CustomUserController($scope) {
// contents of this hook controller is up to you
var _this = this;
// Mixin instance properties.
this.vocalization = getValue('vocalization',$scope.user);
this.runSpeed = getValue('runSpeed' ,$scope.user);
// Mixin instance methods.
this.vocalize = function () {
console.log(this.vocalization);
};
// Mixin scope properties.
$scope.color = color;
// Mixin scope methods.
$scope.run = function(){
console.log("run speed: " + _this.runSpeed );
};
}
function PageController($scope) {
var _this = this;
$scope.user; // this should be the current user obj, with key for custom script lookup
// Mixin Custom Script into Controller.
if (userService.hasCustomScript($scope.user)) {
angular.extend(this, new CustomUserController($scope));
}
}
As for your specific example, one way to insert arbitrary fields into a form is to build it dynamically. I use a schema form directive that might work for your situation. Given a schema that defines the model properties, and an array that specified the items their order of inclusion, the directive lays out the form.
For example (see also this working plunker, incl. add'l features):
<form class="form-inline" name="form" novalidate role="form">
<div class="row-fluid clearfix">
<h2>Sample Form</h2>
</div>
<div class="row-fluid clearfix">
<hr>
<div class="span12">
<fieldset class="span6">
<schema-form-fields
fields="side1Fields"
model="model"
schema="modelSchema"
data="requestResult"
schema-list="schema">
</schema-form-fields>
</fieldset>
<fieldset class="span6">
<schema-form-fields
fields="side2Fields"
model="model"
schema="modelSchema"
data="requestResult"
schema-list="schema">
</schema-form-fields>
</fieldset>
</div>
</div>
<div class="row-fluid clearfix">
<button
class="btn btn-primary span2 offset10"
type="submit">
Submit
</button>
</div>
</form>
// example controller with dynamic form
app.controller('HomeCtrl', ['$scope', 'schema', 'requestResult', 'dataService',
function($scope, schema, requestResult, dataService) {
$scope.modelSchema = schema.product;
$scope.model = {
factoryDate: '20160506'
};
// field name arrays - one array per column in sample layout
// insert elements into these and form should re-render
// may require explicit watch to trigger update
$scope.side1Fields = [
'productName',
'factoryDate'
];
$scope.side2Fields = [
'productType',
'timeValue'
];
// .... other controller code
}
]);
// example schema
app.value('schema', {
"product": {
"type": "object",
"title": "Product Schema",
"properties": {
"productType": {
"type": "string",
"title": "Product Type",
"showLabel": true,
"tooltip": "Product classification",
"readonly": false,
"required": true,
"class": "custom-select",
"enum": ['Bike', 'Car', 'Airplane', 'Glider', 'Stilts']
},
"productName": {
"title": "Product Name",
"showLabel": true,
"type": "string",
"tooltip": "A more descriptive name for the modeled structure.",
"readonly": false,
"required": true
},
"factoryDate": {
"title": "Factory Date",
"type": "string",
"showLabel": true,
"control": "date",
"dateFormat": "yyyymmdd", // TODO format as provided
"tooltip": "Date of manufacture.",
"dateOptions": {
autoclose: true
},
"readonly": false,
"required": true
},
"timeValue": {
"title": "Time (HHMM)",
"showLabel": true,
"type": "string",
"pattern": "([0-1]{1}[0-9]{1}|20|21|22|23)[0-5]{1}[0-9]{1}",
"timeFormat": "hhmm", // TODO format as provided
"tooltip": "Time entry.",
"readonly": false,
"required": true,
}
}
}
});

Related

Loopback: Query by Subdocument Id in Loopback

I would like to have a document which has sub document, which looks like below:
course: {
id,
name,
sections: {
section: {
id,
name
}
}
}
How do i create this model in Loopback?
I don't want to create a separate model for section, because i want to model it as a sub document.
Also, provide some information about how to get the sub document from the sub document id.
for example: if i want to find details about section with id = 2, it should not take in details about course and provide details just about the section.
You can use embedded models.
Here is an example Course.json config using Course embedsMany Section relation.
Please note that you don't need to declare the Section model anywhere else, since it's embedded inside Course.
{
"name": "Course",
"base": "PersistedModel",
"idInjection": true,
"properties": {
"name": {
"type": "string"
}
},
"relations": {
"emails": {
"type": "embedsMany",
"model": "Section",
"property": "name",
"options": {
"forceId": true,
}
}
...
}
Then, to add emails address to a course programmatically, first find an instance of a course then use its generated email property. Again, this is documented
var id = 0;
Course.findById(id, function(err,course) {
course.emails.add({name: 'foo#bar.com'}, function(err, ..) {
//...
}
}

how to make view (form)from json using angular?

I am trying to make view from json .When I have array of objects .I am able to make view and validate that view .
If I have this array of object ,in that case I make able to view ,
check plunker
http://plnkr.co/edit/eD4OZ8nqETBACpSMQ7Tm?p=preview
[{
type: "email",
name: "email",
required:true
}, {
type: "text",
name: "name",
required:true
}, {
type: "number",
name: "phonenumber",
required:true
}, {
type: "checkbox",
name: "whant to check"
},
{
type: "url",
name: "server Url"
}];
Now the problem occurred when i have json object .I need to show view from json object .I don't know from where I will start work
I have this json
"displayName": display the name of label which is from of input text
field.
inputValues :represent the type of tmput filled .if it is number then
user fill only number , text then user only fill number ,email then
user fill email , if it switch then it is drop down with given
option.
"required" give if field is required or not ?
Assuming your JSON is coming from a configuration file or a service, you can start by obtaining the JSON as a JSON object:
angular.module('myapp', [])
.controller('MyController', ['$scope', function($scope) {
$scope.outputs = {};
$scope.rawInput = JSON.parse( '{"studentName": "abc", \
"input": {\
"loginUser": {\
"displayDetail": "UserId for login.",\
"displayName": "Login User Id*",\
"inputType": "TEXT",\
(I had to escape returns to allow the pretty printed JSON string to be parsed)
Once you have that, you are nearly there. Now you can just go the level of JSON that you require and construct your inputs array:
$scope.formInputs = $scope.rawInput['input'];
$scope.inputs = [];
angular.forEach($scope.formInputs, function(value, key) {
/* do something for all key: value pairs */
$scope.inputs.push({"type":value.inputType.toLowerCase(),"name":value.name,"required": value.required})
});
Note you should probably do some error checking here - for the purposes of this example, I don't use any. Here is a working plnkr that demonstrates this code.
I haven't got it all to work - you'll have to construct your select or radio button inputs, but I think you should be able to take it from here.
EDIT I have undated the plnkr to make it public

AngularJS assign variables to view's scope

I have a somewhat deep JSON object I am trying to using in an HTML template.
{
"service": {
"name": "example",
"url": "abc.com",
"template": "/abc/def/v1",
"metadata": {
"password": "dontguessme",
"username": "supereasy"
}
}
}
I am including a template with the following HTML code.
<div class="modal-body" ng-include="service.instructionsTemplate"> </div>
In the template there is the following.
<h1>Some example content</h1>
{{service.metadata.password}}
My question is instead of referencing the field password via service.metadata, is there a way I can reference it with just the variable password.
I was trying to dig through some of the Angular docs around scoping and templates but came up empty. I saw you can use ng-init.
I was able to use ng-init="metadata = service.metadata" and was able to reference the field password in the template via metadata.password.
However I would just like to reference it by password.
Any ideas?
You already did ng-init="metadata = service.metadata", why not going a step further and do ng-init="password = service.metadata.password"?
Another way would be to bind $scope.password = service.metadata.password inside the controller thayou're using on that page
Edit: OP asked a more general solution
If you don't know the property names, then your best move would be to bind the metadata object, like you already did, and then iterate through its properties using ng-repeat
in your controller:
$scope.metadata = service.metadata
in your template (view):
<h1>Some example content</h1>
<ul>
<li ng-repeat="element in metadata">{{element}}</li>
</ul>
You can easily set the password to something inside the controller:
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.data = {
"service": {
"name": "example",
"url": "abc.com",
"template": "/abc/def/v1",
"metadata": {
"password": "dontguessme",
"username": "supereasy"
}
}
};
$scope.password = $scope.data.service.metadata.password;
});
Here is a demo: http://plnkr.co/edit/KrKeLIP2ANd0rl5NLywF?p=preview

How do you update an existing item in an Angular Array (that has changed externally)?

I am new to Angular and am struggling with updating an existing item in my Angular array that has changed externally (not via Angular powered UI).
Here is the use case...My web page is populated via a server side call and I am loading the array into Angular and displaying on a list.
Now, if the data on the server changes and a new record is inserted in the table, my page's JavaScript is notified and it successfully inserts a new records into the Angular array via 'push' (Ref. Programmatically inserting array values in Angular JS).
However, my page is also notified when an existing record is changed (on the server side / not via Angular powered UI). I am drawing a blank about how do I go about updating the correct record in my Angular array? Is there a query / update method that I have missed in the Angular docs?
Here is what my current code looks like...
//The HTML UI updates to reflect the new data inserts.
<div ng-repeat="item in items">
<p class="priority">{{item.priority_summary}}</p>
<p class="type">{{item.type}}</p>
</div>
Here is the Script...
var app = angular.module('DemoApp', []);
<!-- Define controller -->
var contrl = app.controller("MainController", function ($scope) {
$scope.items = [{
"status": "New",
"priority_summary": "High"
}, {
"status": "New",
"priority_summary": "High"
}, {
"status": "New",
"priority_summary": "High"
}, {
"status": "New",
"priority_summary": "High"
}];
//The insert works fine. The question is how do I do an update if the notification is for an update, and not for insert.
$scope.addItem = function(item)
{
alert("addItem called");
$scope.items.push(item);
$scope.item = {};
}
$scope.subscribe = function(){
//Code for connecting to the endpoint...
alert("event received"); //We receive this alert, so event is received correctly.
$scope.items.push({
status: 'New',
priority_summary: 'H'
});
$scope.$apply();
}
//calling subscribe on controller initialization...
$scope.subscribe();
Any suggestions or examples highlighting this would be great. Thanks!
I am assuming that you can retrieve the index of corresponding data you want to update.
So, you can try.
dataList.splice(index, 1);
dataList.splice(index, 0, newItem);
$scope.setActive = function(user) {
User.get({ id: user._id }, function(user){
user.active = true;
user.$update(function(user){
angular.forEach($scope.users, function(u, i) {
if (u._id === user._id ) {
$scope.users[i] = user;
}
});
});
});
};
Angular view update automatically when its model change so no need to do something extra, just update your model in controller for ex-
$scope.data = ajaxdata // data you received from server
and in your view
<ul>
<li ng-repeat="item in data">{{item}}</li>
</ul>
for updating your data you should have some unique key-value pair(u_id in this case) like below code example
$scope.items = [{
"status": "New",
"priority_summary": "High",
"u_id" : 1
}, {
"status": "New",
"priority_summary": "High",
"u_id" : 2
}, {
"status": "New",
"priority_summary": "High",
"u_id" : 3
}, {
"status": "New",
"priority_summary": "High",
"u_id" : 4
}];

extjs form does not get rendered in a panel the second time it is selected

I have a basic layout where different components can be selected using a tree view and then get rendered in the main panel. This works fine for all of my components (like grids) but glitches with forms.
The first time a form is selected it is fine, as soon as you try to select it again nothing gets rendered.
The demo is available here and there is a link to the javascript at the top of the page.
http://www.somethingorothersoft.com/ext
The selection of a component happens in selectNode function and I have tried everything i could without much result.
Edit as Jim Barrows pointed out it would be better to instantiate a component in the create function. I have been hesitant to do that as that is a fairly major change in the my real app and I wanted to actually keep the instances around for ease of navigation between them.
Now that I have written this I realised that to do state properly I would have to store it on the server regardless in case the browser navigates to another page...
Edit I made the change to always instantiate the forms like so, it's much more extJSy now :) :
components['Form1'] = { xtype:'form', "items": [
{ "name": "Rep_ID", "allowBlank": false, "fieldLabel": "Rep" },
{ "name": "Date", "allowBlank": false, "fieldLabel": "Date", "xtype": "datefield" },
{ "name": "Time", "allowBlank": true, "fieldLabel": "Time", "xtype": "timefield"}],
"defaults": { "xtype": "textfield" }
};
components['Form2'] = { xtype: 'form', "items": [
{ "name": "Date", "allowBlank": false, "fieldLabel": "Date", "xtype": "datefield" },
{ "name": "Time", "allowBlank": true, "fieldLabel": "Time", "xtype": "timefield"}],
"defaults": { "xtype": "textfield" }
}
Your problem is here:
var selectNode = function(node) {
node.select();
node = node.attributes;
var newpanel = components[node.component].create();
var cp = Ext.ComponentMgr.get('center_panel');
cp.setTitle(node.text, node.icon);
newpanel.hideMode = 'visibility';
newpanel.hide();
cp.removeAll();
cp.add(newpanel);
cp.doLayout();
newpanel.show();
};
and here:
create: function() { return this; }
The cp.removeAll() does in fact destroy all components. So when the create is called, there is no this to return, so nothing gets shown. The viewport component does automatically destroy anything removed, and the panel inherits this functionality. You can either set autoDestory to false, or do a new inside the create.

Categories

Resources