Binding nested objects with a string key - javascript

I have a project that generates forms based on the data that is passed in.
We support may field types but, for example, here is the input template:
<label>
{{fieldSchema.label}}
<input type="{{fieldSchema.attributes.type}}"
name="{{fieldSchema.attributes.name}}"
ng-model="model[ fieldSchema.attributes.name ]" />
</label>
This works great for flat models, however if the model is nested, it falls apart, eg:
$scope.model = {
manager: {
first_name: 'John'
}
}
$scope.fieldSchema.attributes.name = 'manager.first_name';
Is there a way to use $parse or $interpolate or something similar within the ng-model? I've seen examples on how to fetch data in this structure, but I haven't been able to find a two-way binding solution.
(Note: Using angular version 1.5.0)
Edit: Here is a plunk, hopefully this makes it more clear. http://plnkr.co/edit/4E8jlsnuy5HkCZPxSf5z?p=preview

If template input template html can be controlled by code, then before rendering/appending html to DOM do manipulate, it in below way.
ng-model="model[fieldSchema.attributes.name]"
to
ng-model="model['manager']['first_name']"
Code
function createNgModel(model){
var values = model.split('.')
//and then create a format it do have below format
//model['manager']['first_name']
};
var template = '<label>'+
'{{fieldSchema.label}}'+
'<input type="{{fieldSchema.attributes.type}}"'+
'name="{{fieldSchema.attributes.name}}"'+
'ng-model="+ createNgModel(fieldSchema.attributes.name) +" />'+
'</label>';
Or rather a good option would be just append the string value returned by fieldSchema.attributes.name as suggested by #Amit in comments
var template = '<label>'+
'{{fieldSchema.label}}'+
'<input type="{{fieldSchema.attributes.type}}"'+
'name="{{fieldSchema.attributes.name}}"'+
'ng-model="model.'+ fieldSchema.attributes.name+'" />'+
'</label>';

I ended up doing what Amit suggested in the comments. Thanks, Amit!
First, I created a getter/setter function in my directive:
function getterSetter( newValue ) {
var getter = $parse( 'model.' + $scope.wxFormField.attributes.name ),
setter = getter.assign;
return arguments.length ? setter( $scope, newValue ) : getter( $scope );
}
Then, I changed the ng-model to a that function, and added the ng-model-options directive.
<label>
{{fieldSchema.label}}
<input type="{{fieldSchema.attributes.type}}"
name="{{fieldSchema.attributes.name}}"
ng-model="formFieldFunctions.getterSetter"
ng-model-options="{ getterSetter: true }" />
</label>

Related

why is my list not rendering?

by default it shows 10 elements, but when i change the input it does not update, below is the code and fiddle.
JS Code:
var app = angular.module('myapp',[]);
app.controller('ctrlParent',function($scope){
$scope.listItems = 10;
$scope.newTotal = function(){
$scope.$apply(function(){$scope.lisItemsTotal})
}
$scope.lisItemsTotal = function(num) {
return new Array($scope.listItems);
}
});
http://jsfiddle.net/0dwmqn8y/1/
The reason is that after changing the input $scope.listItems is a string, as the input type is text. Change it to number and all will work. Working plunker: http://jsfiddle.net/yv0z9q8L/
<input type="number" name="red" ng-model="listItems" onchange="angular.element(this).scope().newTotal()">
Also note, that you don't need onchange attribute at all here, however I am unsure whether this is a good thing. Using a function in for ng-repeat is quite dangerous unless you know what you are doing, as this function will be called in each digest cycle. If you are planning to loop over large set of data, it will pretty much kill your performance.
Personally, I would rather go with:
<div ng-app="myapp">
<div ng-controller="ctrlParent">
<input type="number" name="red" ng-model="itemCount">
<ul>
<li ng-repeat="i in items track by $index"><span>{{$index+1}}</span></li>
</ul>
</div>
</div>
var app = angular.module('myapp',[]);
app.controller('ctrlParent',function($scope){
$scope.$watch('itemCount', function(val) {
$scope.items = Array.new(val)
});
$scope.itemCount = 10;
});
plunker: http://jsfiddle.net/m1zq3zqv/1/
The data, that you get from the input field is a string, so you can either change the input type to number or apply a bit of parsing in your code by changing the following line
return new Array($scope.listItems);
to this:
return new Array(parseInt($scope.listItems));

Angular JS - ng-repeat repeating old values

I am new to Angular JS. I have created a code in angular using app and controller. What I am tyring to do is to add name dynamically to a array when a button is clicked.
By default my array has two value passed. When i give an input and click the add button,it adds the string for the first time.
But when i give another input and click add again, the old string is replaced by the new string and the new string is added again.
Here is the piece of code on JSFiddle: http://jsfiddle.net/5DMjt/3680/
var demo= angular.module("demo",[]);
var simplecontroller = function($scope){
$scope.members = [{id:1,name:'prateek'},{id:2,name:'Ruchi'}];
$scope.addmember = function(newmember){
newmember.id = $scope.members.length+1;
$scope.members.push(newmeber);
demo.controller(simplecontroller);
}
}
and here is the HTML Code:
<div ng-app="demo">
<div ng-controller="simplecontroller">
<ul>
<li ng-repeat="member in members">{{member.id}}-{{member.name}}</li>
</ul>
Name<input type="Text" ng-model="inputmember.name">
</br><h2>
{{inputmember}}
</h2>
<input type="button" value="Add" ng-click="addmember(inputmember)">
</div>
</div>
Please Help !
What i analyzed is that push function is passing the address that is why binding still exists.What u can do is pass the value instead like i did below-:
$scope.addmember = function(newmember){
newmember.id = $scope.members.length+1;
$scope.members.push({id:newmember.id,name:newmember.name});
demo.controller(simplecontroller);
}
Hope this solves your problem.Happy learning :)
You have two options.
Either you can reinitialize it every time what I would not recommend.
And the other one is to, pass the parameters with values.
$scope.members.push({id:newmember.id,name:newmember.name});
:)
See this updated fiddle: http://jsfiddle.net/5DMjt/3689/
Name<input type="Text" ng-model="newname">
This gives you a scope variable newname.
<input type="button" value="Add" ng-click="addmember()">
And addmember function uses this newname to create a new object and add it to the list:
$scope.addmember = function(){
var newmember = {};
newmember.id = $scope.members.length+1;
newmember.name = $scope.newname;
$scope.members.push(newmember);
}
You have a syntax error. See console error for more info.
Your variable inputmember is not defined anywhere.
Also you need to push to array new reference of the object, so the old one in array does not change each time you type value.
Here is a working version.
http://jsfiddle.net/a9zvgm8k/
$scope.addmember = function(newMember){
newMember.id = $scope.members.length+1;
$scope.members.push(angular.extend({}, newMember));
demo.controller(simplecontroller);
}
$scope.members = $scope.members.concat({id: newmember.id, name: newmember.name});
Solved : http://jsfiddle.net/5DMjt/3693/
Before pushing to $scope.members you need to create a new object and populate it with id and name from the input.

In Angular models, can you switch value type between JSON Object and String?

I have an application at work that I am working on that requires me to display various fields based on the value of the associated rule.itemType. The issue I am coming across is that I am unable to modify the model data in an ng-repeat if the previous set value in rule.value was a String object that now is displaying fields that require an Object. When I try to assign new values it returns: TypeError: Cannot assign to read only property 'course' of ABC123.
I did find that if the value was an Object it would display it as a String of [object Object], which I am assuming comes from the Object.prototype.toString() function I was reading about, and if changed will replace rule.value with a new String object. Even though this direction works, if I am needing to do a String to Object we end up back at the above mentioned issue.
I have attached sample code to demonstrate what I am trying to do below plus some data. I also forked, modified, and linked a replier's Plunker so you can see it in action:
http://plnkr.co/edit/v4gSyc6MbYeGGyJppvnc?p=preview
JavaScript:
var app = angular.module('plunker', []);
app.controller('appCtrl', function ($scope) {
$scope.rules = [
{id: 1, itemType: 'CS', value: {course: 'ABC', number: '123'}},
{id: 2, itemType: 'SA', value: 'ABC123'}
];
});
HTML, with Angular v1.3.0:
<body>
<div ng-controller="appCtrl as app">
<div ng-repeat="rule in rules">
Rule {{$index+1}}
<!-- Change the itemType for the rule -->
<select ng-model="rule.itemType">
<option value="SA">String</option>
<option value="CS">Object</option>
</select>
<!-- Fields shown if rule.itemType (SA: String, CS: Object) -->
<input type="text" ng-if="rule.itemType === 'SA'" ng-model="rule.value" />
<span ng-if="rule.itemType === 'CS'">
<input ng-model="rule.value.course" />
<input ng-model="rule.value.number" />
</span>
</div>
</div>
</body>
Update 1:
Added the suggestion from Ryan Randall to use ng-change='changeType(rule) which provides the appropriate behavior I was looking for, example plunker below.
http://plnkr.co/edit/bFahZj?p=preview
JavaScript Changes:
// Contributed by: http://stackoverflow.com/a/26702691/949704
$scope.typeChange = function (rule) {
if (rule.itemType === 'CS') rule.value = {};
if (rule.itemType === 'SA') rule.value = '';
};
HTML Changes:
<!-- Change the itemType for the rule -->
<select ng-model="rule.itemType" ng-change="changeType(rule)">
<option value="SA">String</option>
<option value="CS">Object</option>
</select>
One way to avoid the issue you're having is to explicitly set rule.value when rule.itemType changes.
Here's a working plunker containing the tweaks: http://plnkr.co/edit/rMfeBe?p=preview
The following has been added to the select:
ng-change="typeChange(rule)"
And the following has been added to the controller:
$scope.typeChange = function(rule) {
if (rule.itemType === 'CS') rule.value = {};
if (rule.itemType === 'SA') rule.value = '';
};
Don't really understand your question mate... Can you please specify exactly what you did to get that error because it's not happening to me.. (maybe also the browser you're on)
Seems to work fine, see below plunkr
'http://plnkr.co/edit/04huDKRSIr2YLtjItZRK?p=preview'

Remove Quotes from json_encoded string value

I am working on a project to implement jTable into a PHP framwework class.
It is going quite well. Now I am stuck at a problem where I would like to be able to make custom input fieds on the edit dialog.
We are already using the select2 plugin, now I want to implement this on the edit dialogs of jTable. As far as I understood it is possible to add custom fields to the edit dialog like this:
Name: {
title: 'Name',
width: '20%',
input: function (data) {
if (data.record) {
return '<input type="text" name="Name" style="width:200px" value="' + data.record.Name + '" />';
} else {
return '<input type="text" name="Name" style="width:200px" value="enter your name here" />';
}
}
}
Notice the code above is in JavaScript.
Basically what I do is that I build this javascript in php array and via json_encode I send it to the client.
My problem here is that when I do
$column_array['name']['input'] = function (data) {if ....and so on}
I get on the javascript side
input: "function (data) {... so on"
Please notice the double quotes, it is a string and not a function anymore.
What I would need is to remove this 2 double quotes after the input. (well that's my idea so far)
OR if any one got some experience with jTable] and knows a better way to implement custom fields like select2, chosen, jQuery multiselect, elfinder and stuff like that.
I can give tomorrow some code if needed, cause I am not at work anymore today.
Based on this concept:
// Create a function that takes two arguments and returns the sum of those arguments
var fun = new Function("a", "b", "return a + b");
// Call the function
fun(2, 6);
Output: 8
you may change your JSON a bit in to
Name: {
title: 'Name',
width: '20%',
input: { name: "input",
param: "data",
body: "if (data.record) {
return '<input type=\".... "
}
....
then you may define a function and execute it
var d = new Function(Name.input.name, Name.input.param, Name.input.body);
d(otherdata);
this will omit the awful eval(...) stuff. Caveat: it's still not a method of the given object.

Emberjs, data-source, twitter bootstrap typeahead

While this may be specific to the "typeahead" situation, and my example has static content, really this would apply to any bootstrap usage of "data-source". I want to someday when I grow up use dynamic content for my typeahead implementation, so am trying the binding way for now:
Ember.TextField.reopen({
//add some bootstrap specific stuff
attributeBindings: ['data-provide', 'data-items', 'dataSourceBinding:data-source'],
'dataSourceBinding': Ember.Binding.oneWay('App.AddStoreTemplateController.statesArray')
});
I have a router with connectOutlets which attaches my template:
{{view Ember.TextField elementId="state" placeholder="NY/New York" valueBinding="state" data-provide="typeahead" data-items="4" data-source="App.router.addStoreTemplateController.statesArray"}}
My controller:
AddStoreTemplateController: Ember.ArrayController.extend({
statesArray: ['Alabama', 'Washington']
}),
What I expect to see rendered in HTML:
<input id="state" class="ember-view ember-text-field" placeholder="NY/New York" type="text" data-provide="typeahead" data-items="4" data-source="['Alabama', 'Washington']">
What it actually renders in HTML:
<input id="state" class="ember-view ember-text-field" placeholder="NY/New York" type="text" data-provide="typeahead" data-items="4" data-source="App.router.addStoreTemplateController.statesArray">
Typeahead docs
http://twitter.github.com/bootstrap/javascript.html#typeahead
Thanks so much. I really enjoy EmberJS!!
After fiddling with this a bit more, I figured out an easy way to do this. It doesn't require a 3rd party library and you can use Ember.TextField to keep your inputs pretty:
I created a new extended TextField object to keep things separate:
Ember.TextFieldTypeahead = Ember.TextField.extend({
//add some bootstrap specific stuff
attributeBindings: ['data-provide', 'data-items', 'data-source'],
'data-source': function(){
return JSON.stringify(["Alabama", "Washington"]);
}.property()
});
Then in my template:
{{view Ember.TextFieldTypeahead elementId="state" placeholder="NY/New York" valueBinding="state" data-provide="typeahead" data-items="4" data-source=on}}
Things worked fine. Only confusing thing to me, and this may be an Ember bug or just my noob status of the framework, is that data-source= in the template can be anything, it still references the function that I declared. just leaving it as "data-source" in the template yields an error on the handlebars build, so I just opted to make the value "on" so I'm not confused in 6 months time when I revisit the code for some reason. Curious.
I'm also guessing I can extend this even more to observe "value" and then on value change populate the 'data-source' property with whatever ajax call my server responds with to satisfy the dynamic requirement.
You can also do something like this (when you want to load the data dynamically as you type from the server):
ember-bootstrap
EEPD.EbedMedicationArticleTypeAhead = Bootstrap.Forms.TypeAhead.extend({
init: function () {
this._super();
this.set('idProperty', 'id');
},
valueChanged: function () {
var id = this.get('value');
var self = this;
var label = this.get('_childViews')[1].$()
.val();
if (Ember.empty(label) && !Ember.empty(id)) {
var articleDescription = this.get('item.articleDescription');
self.get('_childViews')[1].$()
.val(articleDescription)
.change();
}
} .observes('value'),
getLabel: function (item) {
return '%# (%#)'.fmt(Ember.get(item, 'description'), Ember.get(item, 'amount'));
},
getQueryPromise: function (query) {
//get some data from SignalR
return $.connection.ccprCardioArticles.server.getAllByDescriptionLike(query);
}
});
the handlebar will look like this:
{{view EEPD.EbedMedicationArticleTypeAhead
label="Medicament:"
name="articleNumber"}}
Result:
For this I wouldn't use the Ember.TextField. You could do something like:
<input ... data-items="4" {{bindAttr data-source="formattedDataSource"}}/>
In your controller:
formattedDataSource: function(){
.. format your states array as a string or dump to json...
}.property()

Categories