I have implemented a knockout foreach binding, with multiple templates in the same page, one of the example is given here, what I am interested is in finding out when a block finishes rendering, I have tried afterRender and afterAdd, but I guess it runs for each element, and not after the whole loop is finished.
<ul data-bind="foreach: {data: Contacts, afterAdd: myPostProcessingLogic}">
<li>
<div class="list_container gray_bg mrgT3px">
<div class="list_contact_icon"></div>
<div class="contact_name"><span data-bind="text: first_name"></span> <span data-bind="text: last_name"></span></div>
<div class="contact_number"><span data-bind="text: value"></span></div>
<div class="callsms_container">
<a href="#notification-box" class="notifcation-window">
<div class="hover_btn tooltip_call">
<div class="hover_call_icon"></div>
<span>Call</span></div>
</a>
<a class="sendsms" href="#sendsms" rel="#sendsms">
<div class="hover_btn tooltip_sms">
<div class="hover_sms_icon"></div>
<span>SMS</span></div>
</a>
<a href="#">
<div class="hover_more_btn"></div>
</a>
</div>
<!-- close callsms container -->
<div id="notification-box" class="notification-popup">
<img class="btn_close" src="images/box_cross.png" /> <img class="centeralign" src="images/notification_call.png" /> <span>Calling... +44 7401 287366</span> </div>
<!-- close notification box -->
<!-- close list gray bg -->
<div class="tooltip_description" style="display:none" id="disp"> asdsadaasdsad </div>
</div>
</li>
</ul>
I am interested in finding out just the success callback, when a loop finishes rendering.
here is my afterAdd function, which basically attaches some jQuery events, and nothing much.
myPostProcessingLogic = function(elements) {
$(function(){
$(".list_container_callog").hover(function(){
$(".callsms_container", this).stop().animate({left:"0px"},{queue:false,duration:800});
}, function() {
$(".callsms_container", this).stop().animate({left:"-98%"},{queue:false,duration:800});
});
});
}
thanks in advance, and tell me there is a success callback :)
You have the afterRender callback in knockout.js:
foreach: { data: myItems, afterRender: renderedHandler }
Here's documentation.
Inside your handler check whether the length of the rendered collection is equal to the length of the items collection. If not don't execute the full rendered logic that you intend to use.
renderedHandler: function (elements, data) {
if ($('#containerId').children().length === this.myItems().length) {
// Only now execute handler
}
}
Try wrapping the ul with
<div data-bind='template: {afterRender: myPostProcessingLogic }'>
It will only work the first time everything within the template is rendered. But you will only get the one call to myPostProcessingLogic. Here's a fiddle
<div data-bind='template: {afterRender: myPostProcessingLogic }'>
<ul data-bind="foreach: Contacts">
<li>
<div class="list_container gray_bg mrgT3px">
<div class="list_contact_icon"></div>
<div class="contact_name"><span data-bind="text: first_name"></span> <span data-bind="text: last_name"></span></div>
<div class="contact_number"><span data-bind="text: value"></span></div>
<div class="callsms_container">
<a href="#notification-box" class="notifcation-window">
<div class="hover_btn tooltip_call">
<div class="hover_call_icon"></div>
<span>Call</span></div>
</a>
<a class="sendsms" href="#sendsms" rel="#sendsms">
<div class="hover_btn tooltip_sms">
<div class="hover_sms_icon"></div>
<span>SMS</span></div>
</a>
<a href="#">
<div class="hover_more_btn"></div>
</a>
</div>
<!-- close callsms container -->
<div id="notification-box" class="notification-popup">
<img class="btn_close" src="images/box_cross.png" /> <img class="centeralign" src="images/notification_call.png" /> <span>Calling... +44 7401 287366</span> </div>
<!-- close notification box -->
<!-- close list gray bg -->
<div class="tooltip_description" style="display:none" id="disp"> asdsadaasdsad </div>
</div>
</li>
</ul>
</div>
Chuck Schneider's answer above is the best.
I had to use containerless control as the foreach is on a tbody element:
<!-- ko template: {afterRender: SetupCheckboxes } -->
<tbody data-bind="foreach: selectedItems" id="gridBody">
<tr>
<td>
<input type="checkbox" />
</td>
</tr>
</tbody>
<!-- /ko -->
Just wrap the foreach into another foreach loop using Knockout's container less method like this:
<!-- ko foreach:{data: Contacts, afterRender: myPostProcessingLogic }-->
<ul data-bind="foreach: $data}">
<li>
<div class="list_container gray_bg mrgT3px">
<div class="list_contact_icon"></div>
<div class="contact_name"><span data-bind="text: first_name"></span> <span data-bind="text: last_name"></span></div>
<div class="contact_number"><span data-bind="text: value"></span></div>
<div class="callsms_container">
<a href="#notification-box" class="notifcation-window">
<div class="hover_btn tooltip_call">
<div class="hover_call_icon"></div>
<span>Call</span></div>
</a>
<a class="sendsms" href="#sendsms" rel="#sendsms">
<div class="hover_btn tooltip_sms">
<div class="hover_sms_icon"></div>
<span>SMS</span></div>
</a>
<a href="#">
<div class="hover_more_btn"></div>
</a>
</div>
<!-- close callsms container -->
<div id="notification-box" class="notification-popup">
<img class="btn_close" src="images/box_cross.png" /> <img class="centeralign" src="images/notification_call.png" /> <span>Calling... +44 7401 287366</span> </div>
<!-- close notification box -->
<!-- close list gray bg -->
<div class="tooltip_description" style="display:none" id="disp"> asdsadaasdsad </div>
</div>
</li>
</ul>
<!-- /ko -->
The solution above works great. Additionally, if you need to use the foreach "as" option you can do it as so:
data-bind="foreach: { data: myItems, afterRender: renderedHandlet, as: 'myItem'}">
In version 3.5 Knockout provides events to notify when the contents of a node have been bound
HTML
<div data-bind="childrenComplete: bindingComplete">...</div>
JavaScript
function bindingComplete(){
...
}
If you an create event binding expression at a point in the DOM that encapsulates all your child data binding expression, then it's tantamount to a page binding complete event
reference
https://knockoutjs.com/documentation/binding-lifecycle-events.html
I have just recently made a pull request with knockout for them to add two events to define in the binding, unwrap, then call in the correct spots before rendering the items and after all items have rendered. I haven't heard anything back from them but this does exactly what you want to do but you don't have to write hacky code to get it to work. I'm surprised that nobody has made this request before. I used these callbacks that I added to the source to destroy and reinitialize a knockout bound jquery datatable. This was the simplest solution. I have seen many attempts online that try and do it differently but this is the simplest solution.
Pull request: --> pr 1856
ko.bindingHandlers.DataTablesForEach = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var nodes = Array.prototype.slice.call(element.childNodes, 0);
ko.utils.arrayForEach(nodes, function(node) {
if (node && node.nodeType !== 1) {
node.parentNode.removeChild(node);
}
});
return ko.bindingHandlers.foreach.init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
},
update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var value = ko.unwrap(valueAccessor()),
key = "DataTablesForEach_Initialized";
var newValue = function() {
return {
data: value.data || value,
beforeRenderAll: function(el, index, data) {
if (ko.utils.domData.get(element, key)) {
$(element).closest('table').DataTable().destroy();
}
},
afterRenderAll: function(el, index, data) {
$(element).closest('table').DataTable(value.options);
}
};
};
ko.bindingHandlers.foreach.update(element, newValue, allBindingsAccessor, viewModel, bindingContext);
//if we have not previously marked this as initialized and there is currently items in the array, then cache on the element that it has been initialized
if (!ko.utils.domData.get(element, key) && (value.data || value.length)) {
ko.utils.domData.set(element, key, true);
}
return {
controlsDescendantBindings: true
};
}
};
Knockout Datatables JSFiddle
Try afterRenderAll callback in knockout.js:
foreach: { data: myItems, afterRenderAll: myPostProcessingLogic }
Related
I am stuck in this problem where I have a div tag which updates and shows a list of images. I want to add animation when the value in the div tag is updated in the transition from one set of images to another.
Here as you can see in the bottom there are a set of images for girl's hair. And when the user goes to other tab, a different set of images comes. I want animation in that transition.
The AngularJS part for the transition is as follows :
<div ng-swipe-left="avDesignController.onSwipeLeftAction()" ng-swipe-right="avDesignController.onSwipeRightAction()">
<!-- CUSTOMIZABLE TAB BAR -->
<div class="tab-bar-container" >
<div class="scrollmenutab">
<!-- CUSTOMIZABLE MENU -->
<div ng-repeat="customizable in customizables"
ng-click="avDesignController.onCustomizableClicked($event)"
style="display: inline;">
<a ng-if="customizable.allowed == 1">
<div ng-class="{selected: $index==currentIndex}">
<div ng-if="customizable.name == 'Hair'">
<img class="scrollmenutab-icon"
id="{{customizable.name}}-{{$index}}"
src="/app/app_resources/icons/{{genderImage}}Hair.png">
</div>
<div ng-if="customizable.name != 'Hair'">
<img class="scrollmenutab-icon"
id="{{customizable.name}}-{{$index}}"
src="/app/app_resources/icons/{{customizable.name}}.png">
</div>
</div>
</a>
</div> <!-- MENU : END -->
</div>
</div>
<!-- CUSTOMIZABLES -->
<div class="avdesign-item-container" id="avdesign-item-container">
<div id="four-columns" class="grid-container" >
<!-- LOAD CUSTOMIZABLES IF NOT LAST ITEM IN TAB -->
<ul ng-if="currentIndex < (customizables.length - 1)"
class="rig columns-4">
<li ng-repeat="customizableItem in currentCustomizable.customizable_item">
<img class="tab-icon"
src="/app/app_resources/resources/av/{{avatarInfo.name}}/as/{{customizableItem.as}}"
id="customizable-{{$index}}"
ng-click="avDesignController.onCustomizedItemClicked($event)"
ng-class="{highlight: customizableItem.id==currentID}">
</li>
</ul>
<!-- LOAD OUTFITS (FROM avatarOutfit) IF LAST ITEM IN TAB -->
<ul ng-if="currentIndex == (customizables.length - 1)"
class="rig columns-outfit">
<div ng-repeat="brand in outfitBrand" ng-style="{'margin-bottom':'1vh'}">
<div class="brand-icon" >
<img src="/app/app_resources/icons/{{brand.bg_image}}">
</div>
<li ng-repeat="outfit in brand.outfitList">
<img class="outfit-icon"
src="/app/app_resources/resources/av/{{avatarInfo.name}}/as/{{outfit.as}}"
id="outfit-{{$index}}"
ng-click="avDesignController.onOutfitItemClicked($event,$parent.$index)"
ng-class="{highlightOutfit: $index==avatar.outfit_index && $parent.$index==indexParent}">
</li>
</div>
</ul>
</div>
</div>
</div>
Where the functions being called in the JS part is updating accordingly the images.
So Question being how to add transition animation for the same element when it is updated because we are never leaving or entering that element tag
Your question is answered here
Html
<div ng-controller="c">
<div id={{my_id}} width={{widthOfOutsideWrapper}} height={{heightOfOutsideWrapper}}>
<img ng-src="{{src}}" imageonload />
</div>
</div>
and in controller
var app = angular.module('app', []);
app.directive('imageonload', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.bind('load', function() {
alert('image is loaded');
scope.widthOfOutsideWrapper= this.width;
scope.heightOfOutsideWrapper= this.height;
scope.my_id = "testing";
});
}
};
});
app.controller('c', function($scope) {
$scope.src ="https://www.google.com.ua/images/srpr/logo4w.png";
});
and http://jsfiddle.net/2CsfZ/855/ working example is available here
The above answer is very helpful and might be of a great help for many.
But the answer to my question is you simply can't. ngAnimate's ngEnter and ngLeave will not able to recognize a change unless the element is changed and not what is inside the element.
So either some hack needs to be applied like using ngRepeat with different id's for different updation that happens to the element or something else needs to be done.
<div class="tbody" data-bind="foreach: displayItems">
<div class="t-row">
<div class="t-cell">
<div class="manage-location-buttons">
<a href="javascript:void(0)">
<i class="fa fa-pencil" aria-hidden="true" data-bind="toggleClick: $component.openEditPopup"></i> Edit
</a>
<div class="edit-table-popup" data-bind="visible: $component.openEditPopup">
<ul>
<li><a data-hash="#locationmanagement/managelocations/locationediting" data-bind="click: goToTab">Locations</a></li>
<li><a data-hash="#locationmanagement/managelocations/events" data-bind="click: goToTab">Events</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
It is my sample of custom table.
On Link click I will show edit-table-popup div like popup. Cause I use only one observable openEditPopup for all items, onclick I see popup for each row.
openEditPopup = ko.observable<boolean>(false);
toggleClick - is custom dirrective, which changes boolean value to opposite
Is it possible to use only one observable but to show popup only for clicked row?
Yes, it's possible.
The click binding sends two arguments to an event handler:
The clicked $data,
The click-event.
If your click handler is an observable, this means it calls the observable like so: yourObservable(data, event)
Knowing that an observable is set by calling it with an argument, you can imagine what happens. Note that knockout ignores the second argument.
The solution would therefore be to change the openEditPopup from containing a bool, to containing a displayItem, and changing the visible binding to:
visible: $component.openEditPopup() === $data
An example:
var vm = {
selected: ko.observable("A"),
items: ["A", "B", "C", "D"]
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<p>Tabs</p>
<div data-bind="foreach: items">
<span data-bind="text: $data, click: $parent.selected"></span>
</div>
<p>Content</p>
<div data-bind="foreach: items">
<div data-bind="visible: $parent.selected() === $data">
<h1 data-bind="text:$data"></h1>
</div>
</div>
If I understand correctly, all your rows are bound to one observable and so when you click the row it is setting it to true and all pop ups are showing up?
If so, this would suggest you have a popup per row? I'd recommend changing this and having one popup that is toggled per row and then set it's data to the selected row. Something like this can be achieved with the code below.
var viewModel = function() {
var rows = ko.observableArray([
{id: 1, name: "gimble"}, {id: 2, name: "bob"}, {id: 3, name: "jess"}
]);
var selectedRow = ko.observable();
function showRowPopup(row)
{
//console.log(row.id);
if(selectedRow() == row)
selectedRow(null);
else
selectedRow(row);
}
return {
rows: rows,
showRowPopup: showRowPopup,
selectedRow: selectedRow
}
}
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="foreach: rows">
<div>
<span data-bind="text: id"></span> -
<span data-bind="text: name"></span>
<button data-bind="click: $parent.showRowPopup">Toggle Modal</button>
</div>
</div>
<div data-bind="with: selectedRow">
<h3>My Fake Modal</h3>
<span data-bind="text: id"></span> -
<span data-bind="text: name"></span>
</div>
I have a segment of HTML code that is bound to a knockout foreach loop. Inside of this segment, I have a glyphicon that I want to use to trigger a popover with some custom settings that the user can adjust. Here is the segment of code:
<ul class="nav navbar-nav" data-bind="foreach: items">
<li>
<div>
<a data-bind="attr:{id: itemId}" role="button" data-toggle="popover" title="Config Options" data-content="test content" data-placement="auto right">
<span class="glyphicon glyphicon-cog"></span>
</a>
</div>
</li>
</ul>
I can manually put multiple of these glyphicon anchors in the code outside of the foreach loop and the popovers work just fine. They just don't work inside of a knockout foreach binding...
Does anybody know what I need to do to make the popovers work for items contained in a knockout foreach loop? Any suggestions or pointers on what I may be doing wrong would be greatly appreciated.
UPDATE 1:
So after reviewing Buzinas answer and looking into the issue further, it turns out the issue appears to be with the fact that items is a ko.observableArray that is initialized empty. This array is populated based on user selections. When I add items to the array by default upon initialization, those popovers work. The only ones that don't work are the ones that are added on the fly by users. Any thoughts on this would also be appreciated.
I don't know how it's working for you when not using KO, since the Bootstrap documentation says:
Opt-in functionality
For performance reasons, the Tooltip and Popover data-apis are opt-in,
meaning you must initialize them yourself.
One way to initialize all popovers on a page would be to select them
by their data-toggle attribute:
$(function () {
$('[data-toggle="popover"]').popover()
});
Then, I've tried to do that combining with KO, and everything worked fine:
function AppViewModel() {
var self = this;
self.items = ko.observableArray([
{ itemId: 'id1' },
{ itemId: 'id2' },
{ itemId: 'id3' }
]);
self.add = function() {
self.items.push({ itemId: 'id' + self.items.length });
}
}
ko.bindingHandlers.popover = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
$(element).popover();
}
};
ko.applyBindings(new AppViewModel());
li {
margin-left: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<ul class="nav navbar-nav" data-bind="foreach: items">
<li>
<div>
<a data-bind="attr:{id: itemId}, popover" role="button" data-toggle="popover" title="Config Options" data-content="test content" data-placement="auto right">
<span class="glyphicon glyphicon-cog"></span>
</a>
</div>
</li>
</ul>
<button data-bind="click: add">Add</button>
Update
Since you have problems only when you need to add new items, you can create a Custom Binding, and I created one named popover for you:
ko.bindingHandlers.popover = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
$(element).popover();
}
};
And then, you can use it like this:
<a data-bind="popover">My anchor</a>
I am populating a list of survey questions. On click of a survey question, a modal with graphs of the results pop up. To create my list I am using a knockout template as I need the afterRender function.
Here's my markup:
<div id="priorityMenuW" class="priorityMenuW shadow">
<div class="menuHeader">Select a Survey Question:</div>
<div id="priorityMenu" data-bind="foreach:questionTypes">
<div class="menucategory menuItem" data-toggle="tooltip" data-bind="text:CategoryName, attr:{title:CategoryName}"></div>
<div class="menuitem" data-toggle="tooltip" data-bind="foreach:$root.questions">
<!-- ko if: CategoryName == $parent.CategoryName-->
<div data-bind='template: { name: "question-template",
data:$root.questions,
afterRender: $root.storeQuestionIdOrder }'>
</div>
<!-- /ko -->
</div>
</div>
</div>
And my template:
<code><script type="text/html" id="question-template">
<div class="menuItem" data-toggle="tooltip" data-bind="html:'• '+ $parent.QuestionText, attr:{title:$parent.QuestionText}, css: {'itemSelected' : $root.isPriorityActive($data)}, click: function($data,event){$root.questionChoice($data,event)}"></div></script></code>
My problem is that by sending $data to the function questionChoice, I am receiving an array of all of the templated objects. How can I access the specific object clicked on? I was thinking maybe $data[$index], but that doesn't work.
if you want to use $data[$index], remember that $index is an observable and needs to be evaluated:
$data[$index()]
I am making a Cordova/Phonegap app using Appframework and Knockout js for front and back end. The issue I am running into is occurring in the header of the firstly loaded panel.
The HTML:
<div id="afui">
<div id="content">
<!--CLASS LIST VIEW -->
<div class="panel" title="Classes" id="classList"
data-footer="none" selected="true">
<header>
<div style="float:right" data-bind="click:addClass"
class="button icon add white"></div>
<h1>Classes</h1>
</header>
<ul class="list" data-bind="foreach: classes">
<li data-bind="click:openClass, attr: { id: id, name: name }">
<a href="#categoryList">
<span style="padding-right:20px" data-bind="text: name">
</span>Grade:
</a>
</li>
</ul>
</div>
<!-- CATEGORY VIEW -->
<div class="panel" id="categoryList" title="Class Name">
<header>
<a id="backButton" href="javascript:;" class="button"
style="visibility: visible; ">Back</a>
<h1 data-bind="text: header "></h1>
<div style="float:right" data-bind="click:addCategory"
class="button icon add white">
</div>
</header>
<ul class="list" data-bind="foreach: categories">
<li data-bind="text:name"></li>
</ul>
</div>
</div>
</div>
So in the first panel of id="classList" the addClass button in the header does not get triggered when clicked, tapped, or any other sort of user interaction.
If I tap on a list item with a link to the panel of id="categoryList" and transition to that panel, the button in the top right of that header with event click:addCategory DOES work.
The only way I can get the addClass click event to fire is when I init my models...
ko.applyBindings(classModel, document.getElementById('classList'));
ko.applyBindings(categoryModel, document.getElementById('categoryList'));
is by not specifying the second parameter for the classModel ko binding. Of course this will be a problem, because I have multiple views and need to specify which container each of my models should observe.
VIEW MODELS
ClassModel:
function classInfo(name, id, parent) {
var self = this;
self.name = ko.observable(name);
self.score = ko.observable('');
self.id = ko.observable(id);
self.openClass = function(data, event) {
parent.currentClass(event.currentTarget.name);
};
}
function classListViewModel() {
var self = this;
//class array object
self.classes = ko.observableArray([]);
//current class name
self.currentClass = ko.observable().publishOn("currentClass");
//remove a class from the model
self.removeClass = function(obj) {
//remove the class
};
//add a new class to the model
self.addClass = function() {
//add a new using classInfo object
};
}
So, what is going on here? Is Appframework doing something weird to the header on the main page causing the click:addClass event to not fire?
Any insight/help would be much appreciate, thanks!