I want to create a node tree from a json.
index.html should load node tree recursively from person.json
and now the method is going into infinite loop.
please help me.
app.js
(function() {
var app = angular.module("app", ['directive.cusTree', 'directive.cnode']);
app.controller("Contrl", function($scope, $http) {
$scope.some = function() {
return $http.get('person.json').success(function(data) {
$scope.nodetree = data;
return data;
});
}
});
})();
person.json
{
"person": [{
"id": 1,
"name": "A1" ,
"child": [{
"id": 1.1,
"name": "A11",
"child": [{
"id": 1.11,
"name": "A111"
}, {
"id": 1.12,
"name": "A112"
}, {
"id": 1.13,
"name": "A113"
}]
}, {
"id": 1.2,
"name": "A12"
}, {
"id": 1.3,
"name": "A13",
"child": [{
"id": 1.31,
"name": "A131"
}, {
"id": 1.32,
"name": "A132"
}, {
"id": 1.33,
"name": "A133"
}]
}]
}, {
"id": 2,
"name": "B2"
}, {
"id": 3,
"name": "C3"
}, {
"id": 4,
"name": "D4"
}
]
}
item.html
<div ng-click="show(show)" ng-init="show=true" class="container" ng-repeat="node in nodedata" ng-if="nodedata.length>0">
<ul>
{{node.name}}
<br>
<div ng-if="node.child.length>0">
<custree nodedata="node.child"> </custree>
</div>
</ul>
</div>
index.html
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="app.js" type="text/javascript"></script>
<script src="cusTree.js" type="text/javascript"></script>
<script src="cnode.js" type="text/javascript"></script>
</head>
<body>
<div ng-controller="Contrl as n">
<div ng-init="nodetree=some()">
<div ng-repeat="node in nodetree">
<div class="container">
<custree nodedata="node"> </custree>
</div>
<br>
</div>
</div>
</div>
</body>
</html>
cusTree.js
angular.module('directive.cusTree',[])
.directive('custree',function(){
return{
restrict :'E',
scope: {
nodedata:'='
},
templateUrl:"item.html",
controller:function($scope){
//console.log("new="+ JSON.stringify($scope.nodedata));
}
};
});
If you are creating tree with AngularJS, you have to create 2 directives as below:
app.directive('nodeTree', function () {
return {
template: '<node ng-repeat="node in tree"></node>',
replace: true,
restrict: 'E',
scope: {
tree: '=children'
}
};
});
app.directive('node', function ($compile) {
return {
restrict: 'E',
replace: true,
templateUrl: 'partials/node.html', // HTML for a single node.
link: function (scope, element) {
/*
* Here we are checking that if current node has children then compiling/rendering children.
* */
if (scope.node && scope.node.children && scope.node.children.length > 0) {
var childNode = $compile('<ul class="tree" ng-if="!node.visibility"><node-tree children="node.children"></node-tree></ul>')(scope);
element.append(childNode);
}
},
controller: ["$scope", function ($scope) {
// This function is for just toggle the visibility of children
$scope.toggleVisibility = function (node) {
node.visibility = !node.visibility;
};
// Here We are marking check/un-check all the nodes.
$scope.checkNode = function (node) {
node.checked = !node.checked;
function checkChildren(c) {
angular.forEach(c.children, function (c) {
c.checked = node.checked;
checkChildren(c);
});
}
checkChildren(node);
};
}]
};
});
For More details you can checkout Github Link, its have working demo.
Related
I would like to combine two arrays on the same page to look up the right average rating for each cd, the cd list is in an ng-repeat and they can be matched both with the id_cd. How can i get the right average rating for each cd in the ng-repeat of the cd-listing?
I have the following codes in the landingpage.js
app.controller('LandingPageController',
function LandingPageController($scope, $http) {
function GetAllCdRating() {
$http({
method: 'Get',
url: "/LandingPage/GetAllCdRating"
})
.success(function (data) {
$scope.AllCdRating= data;
// the added section of Saurabh Agrawal
$scope.getValue = function(cd){
for (var i = 0; i < $scope.AllCdRating.length; i++) {
if ($scope.AllCdRating[i].id_cd == cd.id_cd) {
return $scope.AllCdRating[i].averageRating;
}
}
}
})
}
GetAllCdRating();
// looks like this and holds about 200 records for now
[
{
"id": 1,
"averageRating": 7,
"id_cd": 13586
},
{
"id": 2,
"averageRating": 8,
"id_cd": 13540
},
{
"id": 3,
"averageRating": 9,
"id_cd": 13547
},
{
"id": 4,
"averageRating": 6,
"id_cd": 13549
}
]
$scope.GetLastAddedCds = function () {
$http({
method: 'Get',
url: "/LandingPage/GetLastAddedcds"
})
.success(function (data) {
$scope.lastcds = data;
})
}
$scope.GetLastAddedCds ();
// looks like this and holds about 5000+ records
[
{
"id_cd": 13586,
"cdName": "the greatest hits",
},
{
"id_cd": 13606,
"cdName": "live or die",
}
]
}
so far for the body section:
<div ng-controller="LandingPageController">
<div class="srollcell" ng-repeat="cd in lastcds">
<div>{{cd.title}}</div>
<div>
{{getValue(cd)}}
</div>
</div>
</div>
So i added your section Saurabh Agrawal (thanks) but somehow it seems it doesn't count up [i] it stays on '0'
Here is the working example of what you are looking for
Try this
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script type="text/javascript">
var myapp = angular.module('myapp', []);
myapp.controller('FirstCtrl', function($scope) {
$scope.myArray = [
{
"id": 1,
"averageRating": 7,
"id_cd": 13586
},
{
"id": 2,
"averageRating": 8,
"id_cd": 13540
},
{
"id": 3,
"averageRating": 9,
"id_cd": 13547
},
{
"id": 4,
"averageRating": 6,
"id_cd": 13549
}];
$scope.myValues= [
{
"id_cd": 13586,
"cdName": "the greatest hits",
},
{
"id_cd": 13606,
"cdName": "live or die",
}];
$scope.getValue = function(val){
for (var i = 0; i < $scope.myArray.length; i++) {
if ($scope.myArray[i].id_cd == val.id_cd) {
return $scope.myArray[i].averageRating;
}
}
}
});
</script>
</head>
<body>
<div ng-app="myapp" ng-controller="FirstCtrl">
<div ng-repeat="val in myValues track by $index">
{{getValue(val)}}
</div>
</div>
</body>
</html>
I am new to AngularJS and am currently working on an input field, which can accept multiple tags at a time along with the auto-complete feature, which display the available tags as dropdown options. For this I am using the ngTagsInput directive I found on the web(http://mbenford.github.io/ngTagsInput/), which gives me a custom HTML element <tags-input>. This works beautifully:
index.html:
<script>
var app = angular.module('plunker', ['ngTagsInput']);
app.controller('MainCtrl', function($scope, $http) {
$scope.tags = [
{ text: 'Tag1' },
{ text: 'Tag2' },
{ text: 'Tag3' }
];
$scope.loadTags = function(query) {
return $http.get('tags.json');
};
});
</script>
<div ng-app="plunker" ng-controller="MainCtrl">
<tags-input ng-model="tags" add-on-paste="true" display-property="text" placeholder="Add a Tag" add-from-autocomplete-only="true">
<auto-complete max-results-to-show="4" min-length="2" source="loadTags($query)"></auto-complete>
</tags-input>
</div>
tags.json:
[
{ "text": "Tag1" },
{ "text": "Tag2" },
{ "text": "Tag3" },
{ "text": "Tag4" },
{ "text": "Tag5" },
{ "text": "Tag6" },
{ "text": "Tag7" },
{ "text": "Tag8" },
{ "text": "Tag9" },
{ "text": "Tag10" }
]
However I wanted to use the standard HTML <input> element instead of the custom <tags-input> element which comes along with the directive, so with a lot of help and using <script src="https://code.jquery.com/jquery-3.1.0.js" integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk=" crossorigin="anonymous"></script> I was able to do it here:
Here is the new index.html:
<script>
var app = angular.module('plunker', ['ngTagsInput']);
app.controller('MainCtrl', function($scope, $http) {
$scope.tags = [
{ "id":1, "tagname": 'Tag1' },
{ "id":2, "tagname": 'Tag2' },
{ "id":3, "tagname": 'Tag3' },
{ "id":4, "tagname": 'Tag4' }
];
$scope.loadTags = function(query) {
return $http.get('tags.json');
};
});
app.directive('tagsInputAttr',
function($compile){
return {
restrict: 'A',
require: '?ngModel',
scope:{
ngModel: '='
},
link: function($scope, element, attrs, controller) {
var attrsText = '';
$.each($(element)[0].attributes, function(idx, attr) {
if (attr.nodeName === "tags-input-attr" || attr.nodeName === "ng-model")
return;
attrsText += " " + attr.nodeName + "='" + attr.nodeValue + "'";
});
var html ='<tags-input ng-model="ngModel" ' + attrsText + '></tags-input>';
e =$compile(html)($scope);
$(element).replaceWith(e);
}
};
}
);
</script>
<div ng-app="plunker" ng-controller="MainCtrl">
<input tags-input-attr ng-model="tags" add-on-paste="true" display-property="tagname" placeholder="Add tags here..." add-from-autocomplete-only="true">
<auto-complete max-results-to-show="3" min-length="2" source="loadTags($query)"></auto-complete>
</input>
</div>
And the new tags.json:
[
{ "id":1, "tagname": "Tag1" },
{ "id":2, "tagname": "Tag2" },
{ "id":3, "tagname": "Tag3" },
{ "id":4, "tagname": "Tag4" },
{ "id":5, "tagname": "Tag5" },
{ "id":6, "tagname": "Tag6" },
{ "id":7, "tagname": "Tag7" },
{ "id":8, "tagname": "Tag8" },
{ "id":9, "tagname": "Tag9" },
{ "id":10, "tagname": "Tag10" }
]
As you can notice,the new directive tagsInputAttr, which wraps the <tags-input> provides the same functionality and can be used inside <input> tag as an attribute along with the rest of attributes such as ng-model, display-property etc. So I don't have to use the <tags-input> element directly. The problem is that the <auto-complete> placed inside the <input> tag doesn't work.
For this I need to alter my directive, considering the following:
Note: I do not want to use jquery for this
My question is how do I wrap the <auto-complete> inside the same <input tags-input-attr> element:
Either as an attribute inside the same <input tags-input-attr> element
or as an attribute inside a standard HTML element like <div> or <span>, wrapped inside the same <input tags-input-attr> element.
If not the above two, then as last resort, as the <auto-complete> tag wrapped inside the same <input tags-input-attr> element
All help is appreciated.
Thanks in advance.
I made some changes on the previus directive and now it accepts all kind of transformation from attribute to element directive.
You still have the elem-as-attr attribute, but now you have to specify the value of it, that it will be the element that will replace.
Example:
<div elem-as-attr="tags-input"></div>
Your application JavaScript:
var app = angular.module('plunker', ['ngTagsInput']);
app.controller('MainCtrl', function($scope, $http) {
$scope.allTags = [
{ "id":1, "tagname": "Tag1" },
{ "id":2, "tagname": "Tag2" },
{ "id":3, "tagname": "Tag3" },
{ "id":4, "tagname": "Tag4" },
{ "id":5, "tagname": "Tag5" },
{ "id":6, "tagname": "Tag6" },
{ "id":7, "tagname": "Tag7" },
{ "id":8, "tagname": "Tag8" },
{ "id":9, "tagname": "Tag9" },
{ "id":10, "tagname": "Tag10" }
];
$scope.myTags =[
$scope.allTags[2],
$scope.allTags[4],
$scope.allTags[8]
];
$scope.loadTags = function(query) {
return $scope.allTags;
};
});
The directive code:
app.directive('elemAsAttr', function($compile) {
return {
restrict: 'A',
require: '?ngModel',
replace: true,
scope: true,
compile: function(tElement, tAttrs) {
return function($scope) {
var attrs = tElement[0].attributes;
var attrsText = '';
for (var i=0; i < attrs.length; i++) {
var attr = attrs.item(i);
if (attr.nodeName === "elem-as-attr") {
continue;
}
attrsText += " " + attr.nodeName + "='" + attr.nodeValue + "'";
}
var hasModel = $(tElement)[0].hasAttribute("ng-model");
var innerHtml = $(tElement)[0].innerHTML;
var html = '<' + tAttrs.elemAsAttr + attrsText + '>' + innerHtml + '</' + tAttrs.elemAsAttr + '>';
var e = hasModel ? $compile(html)($scope) : html;
$(tElement).replaceWith(e);
};
}
}
});
HTML code:
Element way:
<tags-input ng-model="myTags" add-on-paste="true" display-property="tagname" placeholder="Add a Tag" add-from-autocomplete-only="true">
<auto-complete max-results-to-show="10" display-property="tagname" min-length="2" source="loadTags($query)"></auto-complete>
</tags-input>
Attribute way:
<div elem-as-attr="tags-input" ng-model="myTags" add-on-paste="true" display-property="tagname" placeholder="Add tags here..." add-from-autocomplete-only="true">
<div elem-as-attr="auto-complete" max-results-to-show="10" display-property="tagname" min-length="2" source="loadTags($query)"></div>
</div>
Plunker:
https://plnkr.co/edit/9TqsXy
Note that you cannot use the input element for the tagsInput
because the input element does not have the closing tag in HTML. So
you will not be able to put the auto-complete element inside it.
I have a view model containg an object that is used to display some checkboxes:
components = {
"ComponentInfos": [
{
"Id": "1abb0ee5-7e44-4e45-92da-150079066e99",
"FriendlyName": "Component1",
"LimitInfos": [
{
"Id": "4b7cd37a-2378-4f4f-921b-e0375d60d19c",
"FriendlyName": "Component1 Full",
},
{
"Id": "ff9ebe78-fbe4-4a26-a3df-6ec8e52cd0f2",
"FriendlyName": "Component1 Light",
}
]
}
I am able to create the checkboxes with FriendlyName as label:
<h4>{{l.FriendlyName}}</h4>
<div>
<div ng-repeat="limitinfo in l.LimitInfos">
<label>
<input type="checkbox" ng-model="vm.settings.ComponentInfos[limitinfo.Id]"
value="{{limitinfo.Id}}"/> {{limitinfo.FriendlyName}}
</label>
</div>
</div>
I want to store the selected LimitInfo.Id in an array for each selected checkbox. I was able to store them in an object like this:
settings = {
"ComponentInfos" : {}
};
Result example:
"2e80bedb-4a18-4cc4-bdfd-837ffa130947": true,
"add1edf8-4f11-4178-9c78-d591a6f590e3": true
What I do need is to store the LimitInfo.Idin an array like this:
settings = {
"ComponentInfos" : []
};
Expected result:
"2e80bedb-4a18-4cc4-bdfd-837ffa130947", "add1edf8-4f11-4178-9c78-d591a6f590e3"
I uploaded my code to Plunker.
you can use a ng-click method on the checkbox with a custom controller method to push to that array.
<input type="checkbox" ng-model="vm.settings.ComponentInfos[limitinfo.Id]"
value="{{limitinfo.Id}}" ng-click="toggleSelection(limitinfo.ImpliedLimits)"/>
$scope.toggleSelection = function toggleSelection(item) {
var idx = $scope.vm.settings.ComponentInfos.indexOf(item);
if (idx > -1) {
$scope.vm.settings.ComponentInfos.splice(idx, 1);
}
else {
$scope.vm.settings.ComponentInfos.push(item[0]);
}
};
see this plnkr.
see this answer
One line solution
You can do the following in vanilla JS (ES5 and above, so modern browsers)
var data = {
"a": true,
"b": false,
"c": true,
"d": false,
"e": true,
"f": true
}
var arr = Object.keys(data).filter( key => !!data[key] );
// ['a', 'c', 'e', 'f']
Demo by directive:
var app = angular.module('plunker', []);
app.directive('myCheckbox',function(){
return {
restrict:'EA',
template:'<label>'
+'<input type="checkbox" ng-model="model" ng-change="toggleModel()" /> {{label}}'
+'</label>',
replace: true,
scope:{
label:'#',
value:'#',
output:'='
},
link:function(scope,elements,attrs){
//init checked status
scope.model=scope.output.indexOf(scope.value) > -1;
//binding click replace watch model
scope.toggleModel = function(){
if(scope.model){
scope.output.push(scope.value);
return false;
}
scope.output.splice(scope.output.indexOf(scope.value),1);
}
}
}
});
function MyViewModel()
{
this.components = {
"ComponentInfos": [
{
"Id": "1abb0ee5-7e44-4e45-92da-150079066e99",
"FriendlyName": "Component1",
"LimitInfos": [
{
"Id": "4b7cd37a-2378-4f4f-921b-e0375d60d19c",
"FriendlyName": "Component1 Full",
"ImpliedLimits": [
"ff9ebe78-fbe4-4a26-a3df-6ec8e52cd0f2"
]
},
{
"Id": "ff9ebe78-fbe4-4a26-a3df-6ec8e52cd0f2",
"FriendlyName": "Component1 Light",
"ImpliedLimits": [
"4f74abce-5da5-4740-bf89-dc47dafe6c5f"
]
},
{
"Id": "4f74abce-5da5-4740-bf89-dc47dafe6c5f",
"FriendlyName": "Component2 User",
"ImpliedLimits": []
}
]
},
{
"Id": "ad95e191-26ee-447a-866a-920695bb3ab6",
"FriendlyName": "Component2",
"LimitInfos": [
{
"Id": "8d13765a-978e-4d12-a1aa-24a1dda2149b",
"FriendlyName": "Component2 Full",
"ImpliedLimits": [
"4f74abce-5da5-4740-bf89-dc47dafe6c5f"
]
},
{
"Id": "2e80bedb-4a18-4cc4-bdfd-837ffa130947",
"FriendlyName": "Component2 Light",
"ImpliedLimits": [
"4f74abce-5da5-4740-bf89-dc47dafe6c5f"
]
},
{
"Id": "add1edf8-4f11-4178-9c78-d591a6f590e3",
"FriendlyName": "Component2 Viewer",
"ImpliedLimits": [
"4f74abce-5da5-4740-bf89-dc47dafe6c5f"
]
}
]
}
]
};
this.settings = {
"ComponentInfos" : ["4b7cd37a-2378-4f4f-921b-e0375d60d19c","2e80bedb-4a18-4cc4-bdfd-837ffa130947"]
};
}
app.controller('MainCtrl', function($scope) {
$scope.vm = new MyViewModel();
});
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.3.x" src="https://code.angularjs.org/1.3.20/angular.js" data-semver="1.3.20"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<div ng-repeat="l in vm.components.ComponentInfos">
<h4>{{l.FriendlyName}}</h4>
<div>
<div ng-repeat="limitinfo in l.LimitInfos">
<my-checkbox label="{{limitinfo.FriendlyName}}" value="{{limitinfo.Id}}" output="vm.settings.ComponentInfos"></my-checkbox>
</div>
</div>
</div>
<hr>
<pre>
{{vm.settings | json }}
</pre>
</body>
</html>
You can use the ng-click and make an update of your list.
I've added this to your MyViewModel function and changed the type of your ComponentInfosto an array.
this.update = function (value) {
var exists = false;
for (var elem of this.settings["ComponentInfos"]){
if (elem === value) {
exists = true;
}
}
if(exists) {
var index = this.settings["ComponentInfos"].indexOf(value);
this.settings["ComponentInfos"].splice(index,1);
} else {
this.settings["ComponentInfos"].push(value);
}
}
Additionally you need to change the input in the html to
<input type="checkbox" ng-click="vm.update(limitinfo.Id)"/> {{limitinfo.FriendlyName}}
I am new to angularJS, Try this :
Add this snippet to app.js
this.data =[];
this.selection = function(){
this.data =[];
angular.forEach(this.settings["ComponentInfos"], function(value, key) {
if(value)
this.push(key);
}, this.data);
}
This to body of index.html
<div ng-repeat="l in vm.components.ComponentInfos">
<h4>{{l.FriendlyName}}</h4>
<div>
<div ng-repeat="limitinfo in l.LimitInfos">
<label>
<input type="checkbox" ng-model="vm.settings.ComponentInfos[limitinfo.Id]" ng-click="vm.selection()"
value="{{limitinfo.Id}}"/> {{limitinfo.FriendlyName}}
</label>
</div>
</div>
</div>
<hr>
<pre>
{{vm.settings | json }}
{{vm.data}}
</pre>
I am trying to generate a HTML form.
I have an object which contains an array of form elements like
{
"formFields": [
{
"type": "select",
"label": "Fabric",
"name": "fabric",
"options": [
"Georgette",
"Crepe",
"Net",
"Lace"
]
},
{
"type": "text",
"label": "Weight",
"name": "weight",
"options": []
}
]
}
I want to generate a form which has fields in accordance with the above object i.e. it should generate a Select labelled Fabric with drop down options "Georgette","Crepe","Net","Lace" and an input element of type text with label Weight.
What is the best way to do this in AngularJS?
I would make a directive which accepts a form field object as input and $compiles a template based on the input.
Html:
<div my-input="settings"></div>
Js:
angular.module('myApp').directive('myInput', ['$compile', function($compile) {
return {
restrict: 'EA',
require: 'ngModel',
link: linkFn,
scope: {
config: '=myInput'
}
};
function linkFn($scope, $element, $attrs, ngModelCtrl) {
init();
function init() {
$scope.model = {};
var template = getTemplate();
template.attr('ng-model', 'model.value');
$compile(template)($scope, function(clonedElem) {
$element.html(clonedElem);
});
}
function getTemplate() {
switch($scope.config.type) {
case 'text':
return '<input type="text"/>';
case 'select':
return '<input type="select" ng-options="option in config.options">';
}
}
}
}]);
This is from the top of my head so the code might be wrong but you get the idea.
You can refer to the sample here. Please find the code below:
HTML:
<div ng-app="app" ng-controller="test">
<form name="myForm" ng-submit="validateForm(myForm.$valid)">
<div ng-repeat="item in formData.formFields">
<div ng-if="item.type == 'text'">
<label>{{item.label}}: </label>
<input type="{{item.type}}" name="{{item.name}}">
</div>
<div ng-if="item.type == 'select'">
<label>{{item.label}}: </label>
<select name="{{item.name}}">
<option ng-repeat="opt in item.options" value="{{opt}}">{{opt}}</option>
</select>
</div>
<br>
</div>
</form>
</div>
JS:
var app = angular.module('app', []);
app.controller('test', function ($scope) {
$scope.formData = {
"formFields": [
{
"type": "select",
"label": "Fabric",
"name": "fabric",
"options": [
"Georgette",
"Crepe",
"Net",
"Lace"
]
},
{
"type": "text",
"label": "Weight",
"name": "weight",
"options": []
}
]
};
$scope.validateForm = function(isValid){
/*..*/
}
});
I'm using Knockout JS 3.2 and I'd like to use it with autocomplete dropdown. I'm not able to get around two problems.
I simplified the data and code so this runs stand-alone:
<script type="text/javascript">
var data = [
{ "User": { "Id": 1, "DisplayName": "john a" }, "Roles": [{ "Id": 1, "Name": "admins" }, { "Id": 2, "Name": "users" }] },
{ "User": { "Id": 2, "DisplayName": "john b" }, "Roles": [] },
{ "User": { "Id": 3, "DisplayName": "john c" }, "Roles": [{ "Id": 1, "Name": "admins" }] },
{ "User": { "Id": 4, "DisplayName": "john d" }, "Roles": [] },
{ "User": { "Id": 5, "DisplayName": "john e" }, "Roles": [{ "Id": 2, "Name": "users" }] }
];
$(function () {
$("#searchTerm").autocomplete({
source: data,
minLength: 1,
select: function (event, ui) {
if (ui.item) {
var viewModel = ko.mapping.fromJS(ui.item);
ko.cleanNode($("#userDetails")[0]);
ko.applyBindings(viewModel, $("#userDetails")[0]);
}
}
})
.autocomplete("instance")._renderItem = function (ul, item) {
return $("<li>")
.append("<a>" + item.User.DisplayName + "</a>")
.appendTo(ul);
};
});
</script>
<div>Select User: <input id="searchTerm" name="searchTerm" type="text" /></div>
<div id="userDetails">
<div>User: <span data-bind="text: User.DisplayName"></span></div>
<div data-bind="foreach: Roles, visible: Roles().length > 0">
<div><span data-bind="text: Name"></span></div>
</div>
</div>
Problems:
I'd like to show the userDetails div only when it's bound -- hide it on page load. I tried setting style="display:none" and then data-bind="if:User" or data-bind="if:User.Id". Setting display attribute hides the element on load, but it doesn't change on bind.
Roles element binding doesn't work right. On first time that user is selected, the roles show, but they fail to show after changing the user selection.
Instead of always rebinding you need to have a proper view model with a selectedUser property and just update that one in the automcomplete handler.
var viewModel = {
selectedUser: ko.observable()
}
ko.applyBindings(viewModel, $("#userDetails")[0]);
$(function () {
$("#searchTerm").autocomplete({
source: data,
minLength: 1,
select: function (event, ui) {
if (ui.item) {
var user = ko.mapping.fromJS(ui.item);
viewModel.selectedUser(user);
}
}
})
.autocomplete("instance")._renderItem = function (ul, item) {
return $("<li>")
.append("<a>" + item.User.DisplayName + "</a>")
.appendTo(ul);
};
});
With this approach you can use the with binding and it will also solve both of your problems:
<div id="userDetails" data-bind="with: selectedUser">
<div>User: <span data-bind="text: User.DisplayName"></span></div>
<div data-bind="foreach: Roles, visible: Roles().length > 0">
<div><span data-bind="text: Name"></span></div>
</div>
</div>
Demo JSFiddle.