I have a need to bind some HTML to an object, but my issue is that I don't know the properties of the object at development time.
I have a selectedItem property in my main view model which I have bound to a section in my HTML:
<div data-bind="with: selectedItem">
</div>
Now I want to generate a table based on the property name and property values:
<div data-bind="foreach: [WHAT DO I PUT HERE?]">
<label class="control-label"><span data-bind="text: [OR HERE?]" /></label>
</div>
I have really no idea how to do this. Any help is greatly appreciated.
Also, just slightly extending this, I would like to handle the properties of the bound object differently, such as, if the property is just a primitive type, just output it, but if its another object/array, then handle it specially.
Can this be done?
If anyone else is looking to bind a simple object's properties. You can do it like this...
<table>
<tbody data-bind="foreach: arrayOfObjects">
<tr data-bind="foreach: Object.keys($data)">
<td data-bind="text: $parent[$data]"></td>
</tr>
</tbody>
</table>
note: object.keys is not supported in older browsers, but you can use this to add backwards compatability http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation
Here is a working example using computed observable to select at runtime the data to show. Dynamically selected templates are also used to render the data according to the type of data to render (array or scalar).
Related
This is basically a 2 part question which is why some of the other answers I've found on StackOverflow don't sufficiently cover what I'm asking.
Also need to note: using Bootstrap.
My simplified html can be explained like this:
<div class="container-fluid">
<div class="row"> Headers </div>
<div class="ParentObjectDiv" ng-repeat="pobj in pobjlist">//pobj list is returned from call in controller
<div class="row"> ParentObject values </row>
<div class="ChildObjectContainer">
<div class="row"> Headers </div>
<div class="ChildObjectDiv" ng-repeat="cobj in pobj.cobjs">
<div class="row">
<div class="col-md-1">
<select ng-model="cobj.someproperty"
ng-options="option for option in optionlist">
//Here is the issue : the optionlist is returned from a separate call in the constructor
//The cobj.someproperty can be null, and when that's the case, I want a custom selection
//that says please select a property
</select>
</div>
</div>
</div>
</div>
</div>
</div>
Ok, so on to what I have tried.
Can't do a selectedOption = "whatever" in the controller for obvious reasons.
I have tried adding an angular expression that sets the property of cobj.someproperty to a value like "Value not know" if it detects that the property is empty/null.
This is probably coming down to a fundamental misunderstanding of angular on my part. I have tried putting the expression that assigns this "not known" value to the property in various expression directives and just in the html itself. It is not an option to have this "not known" value on the actual object in the database and the list of Pobj's and their Cobj's is very large so I don't think I can spare iterating the entire list structure to set it before rendering and then iterating it again in the ng-repeats. I'm hoping that I can have a quick expression that evaluates null and sets it. Something like:
{{cobj.someproperty = cobj.someproperty ? cobj.someproperty : "value not known"}}
But not sure if that's a valid expression and if it is where I would put it because so far I've tried putting it in the ng-model itself, an ng-if, and just a blank line of html all within the scope of the cobj in the ng-repeat and none are working.
I haven't "tried" this, but my preferred solution and the one I've been researching is to see if there is a way to instead of evaluating and changing the cobj.someproperty, to change the ng-model of the select to point at a dummy property and at the same time set the cobj.dummyproperty to cobj.someproperty if some property is set, otherwise to set it to "value not known".
Additional considerations :
The parentobject list can be very large (>1k) and has on average 3 childobjects (but can have anywhere from 1 to 20) so performance is an issue.
There will be many cases where the user will not want to set this property so "value not known" when null will not be a temporary thing.
A user also needs to be able to select "value not known" if they have later realize they were mistake about the cobj.someproperty.
I am going to work on the logic to push the changes back to the database later so I am not worried about how the value is stored in the model in angular because I will most likely not use the ng-model to update the field. Right now I'm just worried with the selected option of the drop down.
Here is the answer with simplified html.
JavaScript code
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.items = [{
"someProperty": "abc"
}, {
"someProperty": "xyz"
}, {
"someProperty": null
}]
$scope.optionlist = ["abc","xyz","pqr"];
});
HTML code:
<table ng-controller="MainCtrl" >
<tbody>
<tr ng-repeat="item in items track by $index">
<td width="75%">
{{ item.someProperty }}
</td>
<td width="25%">
<select ng-model="item.someProperty" >
<option value="">Value not know</option>
<option value="{{option}}" ng-repeat="option in optionlist">{{option}}</option>
</select>
</td>
</tr>
</tbody>
So whenever angular finds a null value for someProperty it binds the value to "Value Not Know". I am using ngRepeat instead of ngOption. Let me know if this is what you are looking for.
here is my plunk
I'm just starting to explore with Angular. Now what I'm trying to do is using an mvc handler that returns a jsonobject. However the returned jsonobject is generic, so it can be different types of object, and they all have a different amount of columns. I want to bind all these columns to a table, but that means i can't just create a fixed amount of columns and bind them like so:
<table class="table">
<tr ng-repeat="r in items">
<td>{{r.ID}}</td>
<td>{{r.Name}}</td>
<td>{{r.FirstName}}</td>
<td>{{r.Telephone}}</td>
<td>{{r.Email}}</td>
</tr>
</table>
this is NOT the way i want it, basically i want to have a table that binds to an object and that creates table columns for every property of the object.
thanks for any advice!
You can iterate the properties of the value like
<tr ng-repeat="r in items">
<td ng-repeat="(key, value) in r">
{{ value }}
</td>
</tr>
Most of the time i get some error for 'undefined' property in knockout. I found a solution for the same on stackoverflow answer. It is effective for simple binding, but my question is that how would i use this technique for 'foreach' binding like i have tried like
Demo here
below code is not working
<table>
<tbody data-bind="foreach: model.mappings">
<tr>
<td>
<select data-bind="options:mappings.variableList, optionsText:'Key',optionsValue:'Value', value:mappings.selectedVariable>
</select>
</td></tr></tbody></table>
But below code is working
<table>
<tbody data-bind="foreach:mappings">
<tr>
<td>
<select data-bind="options:variableList, optionsText:'Key',optionsValue:'Value', value:selectedVariable>
</select>
</td></tr></tbody></table>
Js for both is same like:
var list = //some array
var arr =// [{variableList : list}];
var model={
mappings:ko.observableArray(arr)
}
ko.applyBindings......
Imagine your "model" being a function. When binding in html, you are able to access only local variables of model. Model itself is not visible because that is out of your scope. Mappings is a variable in model and that's why you can access it by just writing foreach: mappings. model is not part of model, it is model... Hope this helps.
Also, when you write foreach: mappings then you kind-of enter a foreach loop so that is why you don't write mappings.PropertyName and instead just write PropertyName
edit: my comment on your post was completely wrong so I deleted it
I'm using Angular UI - Bootstrap, specifically the Typeahead directive. http://angular-ui.github.io/bootstrap/#/typeahead
I'm attempting to use the 'typeahead-template-url' to create custom html templates for my suggestion boxes. After trying multiple techniques, unsuccessfully, I discovered that by purposely messing up my quotation marks 'solved' the display issues. I would love to understand why this is breaking and get it working properly.
For example: this works
<table class="> <!--see class quote error -->
<tr>
<td>
<div ng-mouseenter="selectActive($index)" ng-click="selectMatch($index)">
<a>ID{{ match.model.id }} - {{ match.model.text }}</a>
</div>
</td>
</tr>
</table>
This DOESN'T WORK
<table class="">
<tr>
<td>
<div ng-mouseenter="selectActive($index)" ng-click="selectMatch($index)">
<a>ID{{ match.model.id }} - {{ match.model.text }}</a>
</div>
</td>
</tr>
</table>
FIDDLE is here: http://jsfiddle.net/nicktest222/JXtaZ/24/
Additionally, when you select an item in the results list, it returns the entire object. How can I get it to return a specific property in the object?
Any help would be greatly appreciated.
Thanks
I think it is the way you add your template (columnTwo.html) in JSFiddle.
Look at my demo (which is based on yours): http://jsbin.com/aMOrOra/1/edit?html,js,output
As for the typeahead property:
<input type="text" ng-model="monkey" typeahead-template-url="columnTwo.html" typeahead="suggestion as suggestion.text for suggestion in sample_data | filter: $viewValue" />
Here it means that the suggestion object is used as the selected value, but I want to display only the suggestion.text property in the box. But monkey will still be set to the selected suggestion object.
Just so you know, your current filter will look for the query on every properties of the suggestion object, not only text.
EDIT: To filter only the text property, use the filter like this:
filter:{'text':$viewValue}
I've got the following code using Knockout.js to display an array of bools as a series of checkboxes:
<table>
<tr data-bind="foreach: Array">
<td><input type=checkbox data-bind="checked:$data"></td>
</tr>
<tr data-bind="foreach: Array">
<td data-bind="text:$data"></td>
</tr>
</table>
<button data-bind="click: toggle0">Toggle Element 0</button>
<script>
var simpleModel = {
"Array" : ko.observableArray([ko.observable(false),
ko.observable(false),
ko.observable(true)]),
"toggle0" : function() {
simpleModel.Array()[0](!simpleModel.Array()[0]());
}
};
ko.applyBindings(simpleModel);
</script>
If you look at http://jsfiddle.net/tP9Dm/3/, you can see that, while the checkboxes respond to changes in the view-model, the view-model doesn't respond to changes in the checkboxes.
According to https://groups.google.com/d/msg/knockoutjs/-dHpOg5ZBPI/1q4iqdTlKvUJ, it looks like $data is unwrapped by the foreach loop, so data-bind doesn't know to use it to update the model.
Clearly I can fix this by making the array contain objects instead of simple booleans, but that seems like it should be an unnecessary extra layer of indirection. Is there a simpler way to propagate changes back to the model?
There doesn't seem to be a way to do this, because $data in this case is the unwrapped value.
Even setting up a click handler doesn't work, because the data sent to your handler is also unwrapped.
Bummer. Looks like the only way to do this is to wrap your observable into an object, another layer of indirection.