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!
Related
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 }
<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 the simple html page, where simple ul list defined with following viewmodel for it:
<div class="row">
<div class="col-md-6">
<button class="btn btn-link" data-bind="click: addItem">Add item</button>
<ul data-bind="foreach: listItems">
<li>
<p data-bind="text: itemText"></p>
<script>
$(document)
.ready(function() {
console.log("new li has added");
});
</script>
</li>
</ul>
</div>
<div class="col-md-6">
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
</div>
</div>
<script>
function ItemViewModel(text) {
var self = this;
self.itemText = ko.observable(text || "");
}
function ListViewModel() {
var self = this;
self.listItems = ko.observableArray([]);
self.addItem = function () {
self.listItems.push(new ItemViewModel("new item: " + self.listItems().length));
};
}
ko.applyBindings(new ListViewModel());
</script>
My fiddle: https://jsfiddle.net/1c3prhhv/
Adding items to list work perfectly, though script tag in list item template doesn't work. Each time when item adds to list, I need to execute all JavaScript code in corresponding script tag.
How to properly add items to Knockout's observable array with executing JavaScript code in them?
I'm currently building a wizard in asp.net MVC 5 application using knockout. On one of the steps, I need to add items to an html list. that is, text is enter into a textbox and you click the added button, the text is added to the list.
My template:
<script type="text/html" id="addProduct">
<li class="dd-item bordered-inverse animated fadeInDown">
<div class="dd-handle dd-nodrag">
<div class="checkbox">
<label>
<a data-bind="attr: { 'href': '#' }" class='btn btn-xs icon-only success'><i class="fa fa-trash-o"></i></a>
<label data-bind="text: Name, uniqueName: true"></label>
</label>
</div>
</div>
</li>
</script>
<div class="row">
<div class="col-md-6">
<div id="newProduct" class="dd">
<ol class="dd-list" data-bind="template: { name: 'addProduct', foreach: Model.Step1.ProductServices }"></ol>
</div>
</div>
</div>
input textbox:
#Html.TextBoxFor(model => model.Step1.ProductService, new { data_bind = "value: Model.Step1.ProductService")
<button type="button" id="addservice" data-bind="event:{ click: AddProduct }">Add Service/Product</button>
knockout add event:
self.AddProduct = function () {
self.Model.CurrentStep().ProductServices.push({ name: self.Model.CurrentStep().ProductService });
}
self.Model.CurrentStep().ProductServices is a List Product as just one property name.
The code above add an item to the html list and updates the UI, but self.Model.CurrentStep().ProductServices.length is always zero, meaning nothing was added to the Model itself.
Please how can I force it to update the self.Model.CurrentStep().ProductServices?
length is not a property of an observableArray. Check ProductServices().length instead.
I cannot seem to figure this out. I've already tried $(document).ready and still doesn't work for me. I've also tried making a for loop specifically for these two value names to save the results to a var and pass it in that way. I've also tried putting the input with the class and id with search inside of the parent div and outside. Essentially like it in the nav bar. Using list.js & knockout.js btw. Im getting my venues using an ajax request using foursquares api.
JS:
var options = {
valueNames: ['name', 'category']
};
var userList = new List('search', options);
HTML:
<body>
<nav>
<h1 class="formattedH1">Downtown SA,TX</h1>
<span>
<input type="search" placeholder="Search" class="search" id="search">
<input type="button" class="sort searchButton" value="List"></input>
</span>
</nav>
<div id="map" class="map"></div>
<div class="list">
<input type="search" placeholder="Search" class="search" id="search">
<h1>My Top 5</h1>
<!-- Square card -->
<div class="card" id="favViewModel" data-bind="foreach: favList">
<div class="expand">
<h2 class="venueName" data-bind="text:name"></h2>
</div>
<div class="cardSupport" data-bind="attr: {src: image()}">
</div>
<div class="cardSupport" data-bind="text:address">
</div>
<a data-bind="attr: {href: website()}">Website</a>
</div>
<h1>Foursquare Recommended</h1>
<div class="card" id="recViewModel" data-bind="foreach: recommendedSpotList ">
<div class="expand">
<h2 class="venueName" data-bind="text:name"></h2>
</div>
<div class="cardSupport" data-bind="text:location">
</div>
<div class="cardSupport" data-bind="text:category">
</div>
<div class="cardSupport" data-bind="text:rating">
</div>
</div>
<script src="js/tester123.js"></script>
I fixed the same problem with some comments on the github page of the project, just make sure to have the same names as the examples and it will work, everything must be in a <div> and the ul must have the class list
Like this
<div id="hacker-list">
<ul class="list"></ul>
</div>
Link: https://github.com/javve/list.js/issues/9
I found my answer browsing other similar projects, so simple now. Thought it might help someone in case they ran across this. It was that since I was making an ajax call I had to to place the call to ko.applybindings inside the ajax request. The binding was out of scope, if you think about it make's sense especially if your request fails. Why even attempt to still bind the values of the request. HTML as above, and for JS ajax request please see below:
JS:
$.ajax({
url: 'https://api.foursquare.com/v2/venues/explore',
dataType: 'json',
data: 'data',
async: true,
success: function(data) {
venues = data['response']['groups'][0]['items'];
//This is where I had to place the binding to get it to render properly.
ko.applyBindings(new recommendedViewModel(), document.getElementById('recViewModel'));
},
error: function() {
alert("An error occurred.");
}
});
"Hello World" example of this error.
Where?
https://listjs.com/api/#listClass
listClass String, default: "list" What is the class of the
list-container.
Case one (Missing list class):
/* error example */
var options = {
valueNames: [ 'name', 'born' ],
};
var userList = new List('users', options);
<!-- error example -->
<div id="users">
<input class="search" placeholder="Search" />
<button class="sort" data-sort="name">
Sort by name
</button>
<ul data-missing-list-class class="">
<li>
<h3 class="name">John</h3>
<p class="born">1986</p>
</li>
</ul>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/list.js/1.5.0/list.min.js"></script>
Case two (custom listClass missing):
I declare listClass to be no_such_class. The code trying to read childNodes of undefined.
/* error example */
var options = {
valueNames: [ 'name', 'born' ],
listClass: "no_such_class" /* the error */
};
var userList = new List('users', options);
<!-- error example -->
<div id="users">
<input class="search" placeholder="Search" />
<button class="sort" data-sort="name">
Sort by name
</button>
<ul class="list">
<li>
<h3 class="name">No such class</h3>
<p class="born">1986</p>
</li>
</ul>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/list.js/1.5.0/list.min.js"></script>
**the error will not throw under stackoverflow snippet
Extra
Remember to not confuse between:
id or element
Id the element in which the list area should be initialized. OR the
actual element itself.
new List(id/element, options, values);
VS:
listClass
What is the class of the list-container?