I have a Knockout.js viewmodel with a list of products, which are populated by an ASP.NET Web API call:
// Define viewmodel
var myViewModel = {
products = ko.observableArray([]);
};
// Apply knockout bindings
ko.applyBindings(vm);
// Function that obtains product data
function listProducts(viewmodel) {
var productsQuery = "api/products/get";
// Send an AJAX request
$.getJSON(productsQuery).done(viewmodel.products);
}
When I call listProducts, each element is added successfully to the ff. HTML <ul>:
<ul data-bind="foreach: products">
<li class="item">
<data-bind="text: productName">
</li>
</ul>
However, when each item is added, the jQuery function that applies to my .item elements:
$(".item").click(function () {
$(this).toggleClass("selected");
});
does not get applied to these newly added elements.
Question: How do I add elements to the Knockout.js observableArray that will also inherit their corresponding jQuery methods?
Basically you need to go back to the knockout docs and read, read, read! Forget doing this with jquery, it's just not the ko way. Knockout frees you from this kind of coding style, you just need to grok it.
hint:
use the click binding and the css binding
http://knockoutjs.com/documentation/css-binding.html
As an aside you can also do this:
$.getJSON(productsQuery).done(viewmodel.products);
Related
I have posted more detailed question this is much clear and straightforward
here
hello, I am trying to set the value of list element to false by using knockout
this is my HTML
<li>
<a onclick="log(this)" data-bind="visible: true, text: $data"></a>
</li>
is there a way to say something like this :
myViewModel.items()[i].setVisible(false);
Don't set the visible binding to true set it to the variable you define in your viewModel. Also you can access individual elements of an observable array through the foreach binding. Lastly, if you want to use $data you can access the property of the individual array object directly using the "." operator. The documentation I referenced at the end of my post has more information. See below:
<div data-bind="foreach: shouldShowMessageArray">
<div data-bind="visible: $data.shouldShowMessage">
Message goes here.
</div>
</div>
<script type="text/javascript">
var myViewModel;
$(document).ready(function(){
myViewModel = new viewModel();
function viewModel() {
this.shouldShowMessage = ko.observable(false) // Message initially visible
this.shouldShowMessageArray = ko.observableArray([
{shouldShowMessage: ko.observable(true)},
{shouldShowMessage: ko.observable(false)}
]);
}
ko.applyBindings(myViewModel);
});
</script>
Knockout foreach / $data documentation
If you are looking to load the page with a starting value of visible:false, you may run into some issues with the "visible" binding.
I have had trouble with the "visible" binding when I want an element to be hidden on page load, and then made visible after some action taken by the user. You will see a flash of the hidden element while your knockout.js logic loads if you are using the visible binding. If you try to use inline CSS to set display:none on the element, then it will never become visible even when the KO logic results in a visible:true value. This is because KO applies the CSS rules after it has handled changing the element.style.display value.
One solution to this issue that I have found is to set up a CSS class that well set display to none, and then use a data binding to conditionally apply that CSS rule based on an observable.
CSS:
.hideMe { display: none; }
HTML:
<div class="hideContent" data-bind="css: { hideContent: !yourObservable() }">
<p>Content</p>
</div>
I need to get a dynamically created list item within unordered list to simultaneously be animated while being created.
My JQuery code setup:
$("button#enterAction").click(function(){
var userAction = $('input#action').val();
$("ul.list-group").append('<li class="list-group-item" id="added-list-item">'+userAction+'</li>');
});
Now this code above creates a list item without animation to it. How do I add animation (say .toggle or .animate) to only this list item without affecting my whole unordered list?
I thought it could be achieved by firing a second event on dynamically created element with an ID added-list-item but I am not sure how to correctly write it down (syntax and functions used). Please help.
You can add a hidden element and then fade it out:
$("ul.list-group").append('<li class="list-group-item" style="display:none">'+userAction+'</li>').children(':last').fadeIn();
Working example:
$(function() {
$("button#enterAction").click(function(){
var userAction = $('input#action').val();
$("ul.list-group").append('<li class="list-group-item" style="display:none">'+userAction+'</li>').children(':last').fadeIn();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" id="action">
<button id="enterAction">Add</button>
<ul class="list-group"></ul>
There are two ways of binding events Direct and Delegated. You are using the direct method which applies on existing elements only. To let event listener work on dynamic elements you'll need to use the second method.
Direct method: $(selector).click(callback);
Delegated method: $(selector).on('click', callback);
Delegated method will work for you.
you could bind click to your new created item.
here is an example
$("button#enterAction").click(function(){
var userAction = $('input#action').val();
var li = $('<li class="list-group-item" id="added-list-item">'+userAction+'</li>'); // create li
li.bind("click", function(){
/// code goes here
})
$("ul.list-group").append(li);
});
I created a custom knockout binding that wraps a given div in an expander. My custom binding moves the given content div to the contant-container div of the expander. After moving the content, the knockout bindings of the content child nodes would not work any more (e.g. click binding for a button inside the content div). Therefore I have to reapply the knockout bindings.
This works in most cases. However, If the content div contains for example a knockout foreach binding, reapplying the bindings means that some content is duplicated.
Example usage of the expander binding:
<div
data-bind="expander: { title: 'dataCollectionForms'}">
<div>
<div class="expander-content">
<button
data-bind="click: $root.addAction, text: 'Hinzufügen'"></button>
<div
data-bind="foreach: listOfButtons">
<button
data-bind="click: $root.buttonClickAction">
</button>
</div>
</div>
</div>
</div>
My code for moving the content div:
function moveExpanderContentToContentContainer($contentContainer, expanderContent) {
try {
//Move the expander content to the content container
//The content will not be cloned but moved. For more info see:
//https://api.jquery.com/append/
var $expanderContent = $(expanderContent);
$contentContainer.append($expanderContent);
$contentContainer.addClass('expander-content-container panel-body');
$expanderContent.addClass('expander-content');
ko.applyBindingsToDescendants(bindingContext, expanderContent);
} catch (appendException) {
var errorMessage = 'Could not append expander-content to expander-content-container.';
logger.logError(errorMessage, appendException, self, true);
}
}
If I remove the line
ko.applyBindingsToDescendants(bindingContext, expanderContent);
the click actions of my three buttons do not work any more:
If I keep the line, the click actions work but the buttons are duplicated:
=> How can I update the bindings of the moved content in a way that fixes
the click bindings and does not duplicate my buttons?
=> If this moving work flow does not work at all, what is a better way to create a custom knockout binding that wraps a given content in a collapsable expander?
I could find some related articles but no solution to my specific issue:
http://knockoutjs.com/documentation/custom-bindings-controlling-descendant-bindings.html
How to clear/remove observable bindings in Knockout.js?
ko.applyBindingsToNode vs. ko.applyBindingsToDescendants
Remove knockout js bindings on cloned element
https://github.com/knockout/knockout/issues/1821
I solved the issue by not moving the content div at all but building the expander around it.
My original strategy was to have a reusable expander view + view model and to move the content div from the original location to the newly composed expander view. Moving around the already bound content was no good idea, I guess.
The new strategy adapts the already existing divs and composes only the header for the expander.
Nevertheless thank you for your thoughts.
I use following code for recreating content of the passed DOM element (having view model + template selector):
function renderIntoElement(element, viewModel, templateSelector) {
templateSelector = templateName || "#myTemplateId";
var $element = $(element);
var templateHtml = $(templateSelector).text(),
$element.children().remove();
$element = $element.append(templateHtml),
ko.applyBindings(viewModel, $element.children()[0]);
}
Hope this helps.
I am new to angularjs, just like many other developer, I was jquery developer. Now I have a question about directive.
example: if i have a directive like this:
app.directive('mydiv',function(){
return{
restrict:'AE',
template:'<div><ul><li></li><li></li><li></li></ul></div>', //of course in real case it will be use ng-repeat
link:function(scope,element,attr){
}
}
})
the confuse I have is,if I need to access any element,in jquery we can use $(this), how can we do it with angular way? Can I do like this:
link: function (scope, element, attrs) { //when hover show the delete icon for each msg and apply highlight class to it.
element.find('li').hover(
function(){
$(this).addClass('highlight');
scope.deleteicon=true;
},
function(){
$(this).removeClass('highlight');
scope.deleteicon=false;
});
}
You can access the element as the 1st argument (element argument in your case) of the link function itself. If you are using jquery with angular and loading jquery before angular the element` will be wrapped in jquery wrapper, i.e it will be a jquery object. If not angular uses a lighter subset of jquery called jqlite. It only provided a minimal functionality.
See element for details.
Instead of binding hover event manually you should use angular event binding and use ng-class instead of add/remove class. That way you perform things angular way and you do not need to manually invoke digest cycle via scope.$apply() for DOM updates with respect to scope updates (Which you need to do in the hover pseudo event in your example to reflect DOM updates based on scope property deleteicon).
An example implementation of your directive will look like this. There are numerous ways you can do this using angular built-in directives itself.
.directive('mydiv',function(){
return {
restrict:'AE',
scope:{items:"="}, //<-- data bound from parent scope via attribute
template:'<div><ul><li ng-mouseenter="toggleClass(true)" ng-mouseleave="toggleClass(false)" ng-class="{'highlight': action.deleteicon}" ng-repeat="item in items">{{item.name}}</li></ul></div>', //of course in real case it will be use ng-repeat
link:function(scope,element,attr){
scope.action = {deleteicon :true};
scope.toggleClass = function(show){
scope.action.deleteicon = show;
}
}
}
});
scope:{items:"="}, sets up 2 way binding if you wish to bind the data from the parent scope via attribute.
Instead of repeating li use a data model, say an array of items and use ng-repeat instead of duplicating the tag (unless you need to do it for sure). ex:- ng-repeat="item in items"
Use angular event bindings instead of binding the events manually, hover is nothing but nouseenter/mouseleave. So you can use respective angular directives and bind a function on the scope. i.e ng-mouseenter="toggleClass(true)" ng-mouseleave="toggleClass(false)".
Use ng-class bound to a scope variable to toggle the class. Let angular manage DOM manipulation for toggling the css class on the element. You just worry about the data being bound. i.e ng-class="{'highlight': action.deleteicon}"
You can find official documentation on angular built in directives/components.
Well, you used hover, but, you can use the MouseOver Directive.
As the following example:
<li ng-mouseover="high = true" ng-class="{'highlight':high}">Content</li>
Look at this plkr
Markup:
<div class="row" ng-app="myapp" ng-controller="myctrl">
<div class="col-lg-3 col-lg-push-3">
<ul class="list-group">
<li class="list-group-item" ng-init="myclass='hide'" ng-repeat="item in items"
ng-mouseenter="myclass = 'show'" ng-mouseleave="myclass='hide'">
<span>{{item}}</span>
<span ng-class="'pull-right glyphicon glyphicon-edit '+myclass"></span>
</li>
</ul>
</div>
</div>
Script:
angular.module('myapp', [])
.controller('myctrl', function ($scope) {
$scope.items = ['abc', 'xyz', 'klm'];
});
Scenario
I have a list of users, within each list item is a <header> and a <div> wrapper - they are siblings:
<li class="user">
<header>
// ...
</header>
<div class="user-wrapper">
// ...
</div>
</li>
I am toggling the div wrapper when the user header is clicked.
What currently works:
// the handler is called
$('li.user > header').live('click', function () {
$(this).next('.user-wrapper').toggle();
});
As live() has been deprecated and I am using jQuery 1.7.2, I want to use on().
What does not work:
// Direct style
$('li.user > header').on('click', function () {
$(this).next('.user-wrapper').toggle();
});
// Delegated style
$('li.user').on('click', 'header', function () {
$(this).next('.user-wrapper').toggle();
});
In either on() scenario, the anonymous handler function is not being called.
Question
First... why is on() choking?
Second... if/when it works, using the delegate style, can i still reference the header's sibling div in the same manner as above?
[EDITED]
For delegate event handling the syntax of .on() is:
// 'element' must be static and an ancestor to 'target'.
$(element).on(eventName, target, handlerFunction);
Your delegate scenario above should work, assuming your li.user tags are static at the time of binding.
$('li.user').on('click', 'header', function () {
$(this).next('.user-wrapper').toggle();
});
If you test this in jsFiddle, it works as is. It seems like your li.user elements are being created dynamically.
If li.user is dynamically created then use a different (static) parent selector. If your list ul is always present, for example:
// HTML
<ul class="user-list">
<li class="user">
<header>
// ...
</header>
<div class="user-wrapper">
// ...
</div>
</li>
</ul>
// JavaScript
$('ul.user-list').on('click', 'li.user > header', function() {
$(this).next('.user-wrapper').toggle();
});
Thanks
Lots of gratitude to #thecodeparadox - his answer explains the issue best and is marked as such. My answer below just explains the details of how/why the list items became dynamic.
Further explanation
Knockout.js is being used to template the li elements. This alone was not the issue as the li elements were created before the .on() bindings occurred and are still treated as "static".
The killer was a ko.computed() property on the knockout model. The property reloads the collection here/there. From the moment the collection is reloaded, the scaffolding of the li items is redone and all the .on() break.