Is there a way to apply a single view model to several elements?
Basically I have a section of html that the view model (VM1) should be bound to, and I have another view model (VM2) that needs to be bound to a subsection of this. KO doesn't seem to like this though (I even tried using ko.cleanNode(element) on the subsection). So what I'm trying to do is be more specific in my binding by applying it to each piece that needs it. This is hard to explain without code, so here we go:
<section>
<ul>
<li id="one">...</li>
<li id="two">...</li>
<li id="three">...</li>
<li id="diffmodel">...</li>
</ul>
</section>
What I currently have is VM1 being bound to <section>, and VM2 being bound to #diffmodel, but KO doesn't seem to like this.
My current objective (and the question proposed) is to apply VM1 to #one, #two, and #three, and VM2 to #diffmodel, but that doesn't seem to work either (VM1 isn't being bound at all).
Is there a nice solution to this type of situation?
A pretty easy solution is to use a custom binding to prevent the children of an element from being bound.
It would be something like:
ko.bindingHandlers.ignoreBindings = {
init: function() {
return { controlsDescendantBindings: true };
}
};
var VM1 = {
valueOne: ko.observable("one"),
valueTwo: ko.observable("two"),
valueThree: ko.observable("three")
};
var VM2 = {
different: ko.observable("different")
};
ko.applyBindings(VM1);
ko.applyBindings(VM2, document.getElementById("diffmodel"));
HTML:
<section>
<ul>
<li id="one" data-bind="text: valueOne"></li>
<li id="two" data-bind="text: valueTwo"></li>
<li id="three" data-bind="text: valueThree"></li>
<li data-bind="ignoreBindings: true">
<div id="diffmodel" data-bind="text: different"></div>
</li>
</ul>
</section>
Sample here: http://jsfiddle.net/rniemeyer/FesgK/
In KO 2.1 (in RC at the moment), the ignoreBindings custom binding could even be used as a containerless binding like: http://jsfiddle.net/rniemeyer/FesgK/1/
Related
I have prepared a small jsfiddle here: http://jsfiddle.net/zb8jwre6/
Basically, I have observable array of sliders, and each slider should have it's own observable array of segments, which contain some properties for CSS-binding in HTML.
My first problem is that I'am not sure which foreach bind should i use:
This one doesn't work for some reason:
<div data-bind "foreach: $root.sliders">
<p data-bind="text: day"></p>
</div>
This one works, but I am not sure in which cases should I use this one:
<!-- ko foreach: sliders-->
<p data-bind="text: day"></p>
<!-- /ko -->
My second problem is that I don't know how to apply wanted CSS stylings from segment observable array.
I have tried this:
<div class='slider-segment' data-bind= "style: {left: segment_left, width:
segment_width, backgroundColor: segment_color}"></div>
But this does not work. I think I need to make those properties also as observables, but I am not sure how to do this properly in ViewModel
I would like to know what foreach binding should I use. When can I use "comment" foreach bindng and when do I use normal one, and I would like to know how to rework my code, so I can bind CSS properties from segments observable array.
Thank you!
I've changed
self.segments = ko.observableArray([segments]);
with
self.segments = ko.observableArray(segments);
See:
http://jsfiddle.net/x4a8pkmu/
I would like to know what foreach binding should I use. When can I use
"comment" foreach bindng and when do I use normal one, and I would
like to know how to rework my code, so I can bind CSS properties from
segments observable array
The "comment" syntax is useful if you do not want a container element. For example:
<ul>
<!-- ko foreach: myList -->
<li data-bind="text: myProp"></li>
<!-- /ko -->
</ul>
produces the same effects as:
<ul data-bind="foreach: myList">
<li data-bind="text: myProp"></li>
</ul>
The point of making a variable an observable is if you are going to change these values based on user interaction/server response, and then updating the UI. If the values are never going to change then using an observable for the style properties isn't helpful.
There is a very small difference between the two foreach loops - 'Comment' foreach does not have a parent div tag around the repeating child tags, while the other one does. So the outputs would look like:
Comment foreach:
<p>MON</p>
<p>TUE</p>
<p>WED</p>
Div foreach:
<div>
<p>MON</p>
<p>TUE</p>
<p>WED</p>
</div>
The comment foreach is useful for cases like these:
<ul>
<li class="header">Header item</li>
<!-- ko foreach: myItems -->
<li>Item <span data-bind="text: $data"></span></li>
<!-- /ko -->
</ul>
I am new to Angular and may be missing something obvious. I did read the ng-model scope documentation. And followed the recommendation to use an object instead of primitives.
I also tried the $parent suggested by a few others here. That didn't help either.
I have read many Q&As here involving ng-repeat. But they are talking about an input inside an ng-repeat. In my case there is only one text box. I am trying to search inside a tree using ng-repeat.
<script type="text/ng-template" id="tree_item_renderer.html">
{{data.name}}
<ul>
<li ng-repeat="data in data.nodes" ng-include="'tree_item_renderer.html'" ng-show="visible(data)"></li>
</ul>
</script>
This is the function being called:
$scope.visible = function(item) {
console.log("SearchText inside visible: " + $scope.input.searchText);
return !($scope.input.searchText && $scope.input.searchText.length > 0
&& item.title.indexOf($scope.input.searchText) == -1);
};
Take a look at my jsFiddle
I saw your code on the jsFiddle. There is an issue that you called ng-controller="TreeController" twice and initialized ng-app = Application. I have commented out these two lines of code, and you can see the change on the console.
The reason why you are getting this problem is you make Angular to re-initialize TreeController which it clears the value of input.searchText.
Check out the working jsFiddle !
<div ng-app="myApp">
<div ng-controller="TreeController">
Search UIC:
<input type="text" ng-change="search()" ng-model="input.searchText"
placeholder="Enter your search terms" />
<!-- <ul ng-app="Application" ng-controller="TreeController"> -->
<ul>
<li ng-repeat="data in tree" ng-include="'tree_item_renderer.html'" ng-show="visible(data)">
</li>
</ul>
</div>
</div>
I'm using angular-ui-tree.
The object I have in my treeview has a sort index. I am looking for a way to bind this sort index to the desIndex of the treeview scope, using something similar to this:
ui-tree-desIndex="node.sortIndex"
desIndex is the treeview node's index and node.sortIndex is my object's index.
I want it to sort my list by my objects' values, and when I move an object in the treeview the scope will update that object's index like this:
<div ui-tree="treeOptions" callbacks="treeOptions">
<ol ui-tree-nodes="" data-nodrop-enabled="true" ng-model="rootNodeLst" callbacks="treeOptions" id="tree-root">
<li ng-repeat="node ui-tree-desIndex="node.sortIndex" in rootNodeLst" callbacks="treeOptions" ui-tree-node ng-include="'nodes_renderer.html'"></li>
</ol>
</div>
The code above isn't working, how can I fix it?
Change this
<li ng-repeat="node ui-tree-desIndex="node.sortIndex" in rootNodeLst" callbacks="treeOptions" ui-tree-node ng-include="'nodes_renderer.html'"></li>
To this
<li ng-repeat="node in rootNodeLst" ui-tree-desIndex="node.sortIndex" callbacks="treeOptions" ui-tree-node ng-include="'nodes_renderer.html'"></li>
For starters, to get your ng-repeat working.
Then, if ui-tree-desIndex does not get the value set, you can try:
ui-tree-desIndex="{{node.sortIndex}}" or ng-attr-ui-tree-desIndex="{{node.sortIndex}}"
See this JSFiddle.
Funny thing.
Found out that the way im building the treeview with the nested html modify my $index to show this sort number, so, anyway. thanks for your time and helå
Good morning,
I'm trying to change the limitTo filter on a certain list, my issue is:
when I click to the trigger who change the filter limit the filter changes on all ng-repeated categories.
my function inside the main controller
$scope.showMore = function(limit) {
if($scope.limitItems === $scope.itemsPerList) {
$scope.limitItems = limit;
$scope.switchFilterText = 'less';
} else {
$scope.switchFilterText = 'more';
$scope.limitItems = $scope.itemsPerList;
}
}
my scenario (I rewrote it in a simplified version)
<li ng-repeat="item in category.items | limitTo: limitItems ">
{{item.title}}
</li>
<li ng-if="limitItems < (category.items.length)">
<a ng-click="showMore(category.items.length)" >Show {{ switchFilterText }}</a>
</li>
Could you explain me what's wrong with me?
I searched how to select a single element to apply the function but I didn't find anything useful
Update:
I found the way to solve my issue in this way:
No functions inside the controller are involved to make this functionality works properly:
<li ng-repeat="category in maincategories" ng-init="limitItems = maxItemsPerList">
{{category.title}}
<ul>
<li ng-repeat="item in category.items | limitTo: limitItems "> {{item.title}}
</li>
</ul>
<a ng-click="limitItems = category.items.length" href>
<b ng-if="category.items.length > maxItemsPerList && limitItems != category.items.length "> Show more </b>
</a>
I'm not really convinced about Angular (I used it in my past and I was impressed by the performance but now I can see logics senseless):
What I learned:
ng-if and ng-click cannot be used in the same content because ng-if creates new scopes so if you put ng-if on top of the "show more" link it will break the code
ng-init cannot be used in the same element of the ng-repeat otherwise the var initialised will not be available inside the ng-repeat block
I think there is another way to do that, maybe more clean but in this specific case I can't do a lot.
ng-if and ng-click cannot be used in the same content because ng-if
creates new scopes so if you put ng-if on top of the "show more" link
it will break the code
Yes, ng-if creates a new scope, but it is possible to mix ng-if and ng-click (and most other directives). To do that, you'll be safer if you always write to atributes of another object instead of a simple variable. It is plain JavaScript prototypal inheritance in play.
<li ... ng-init="category.limitItems = maxItemsPerList">
ng-init cannot be used in the same element of the ng-repeat otherwise
the var initialised will not be available inside the ng-repeat block
True, in the sense that variables are created in the local scope. But again, refer to an object.
I think there is another way to do that, maybe more clean but in this
specific case I can't do a lot.
You don't need to do a lot, it is quite simple to do it right actually.
Some advices:
Use ng-init with care. I know it will tempt us but always try to put logic inside controllers and services;
Avoid assignments inside templates;
Learn how to use controllerAs syntax. It gives you an object to write your models to (the controller), so solves most problems related to scope inheritance;
Do not inject $scope, put your view models inside controllers.
Full code goes like this:
<li ng-repeat="category in maincategories" ng-init="category.limitItems = maxItemsPerList">
{{category.title}}
<ul>
<li ng-repeat="item in category.items | limitTo: category.limitItems "> {{item.title}}
</li>
</ul>
<a ng-if="category.items.length > maxItemsPerList && category.limitItems != category.items.length" ng-click="category.limitItems = category.items.length" href>
<b> Show more </b>
</a>
I was trying to see if there is a way to call a function I designed inside the scope:
<ul class="ui-listview ui-radiobutton" ng-repeat="meter in meters">
<li class = "ui-divider">
{{meter.DESCRIPTION}}
{{htmlgeneration}}
</li>
</ul>
$scope.htmlgeneration = function()
{
...
}
The function is called htmlgeneration. Essentially, what I want to do is dynamically append HTML inside the li element while using AngularJS.
Yep, just add parenthesis (calling the function). Make sure the function is in scope and actually returns something.
<ul class="ui-listview ui-radiobutton" ng-repeat="meter in meters">
<li class = "ui-divider">
{{ meter.DESCRIPTION }}
{{ htmlgeneration() }}
</li>
</ul>
I guess my problem was related to conflicts with Django tags. This post was helpful.
What worked for me was a simple solution involving using ng-bind and changing the code to something like this:
<ul class="ui-listview ui-radiobutton" ng-repeat="meter in meters">
<li class="ui-divider" ng-bind="htmlgeneration(meter.DESCRIPTION)">
</li>
</ul>