The first time i click the Button everything is bound correct. But when i click the button a second time all the old values are still bound to my view? How can i reapply new bindings to my breeze.js viewmodel?
JS
var manager = new breeze.EntityManager('/breeze/corporations');
$("#myButton").click(function () {
var query = breeze.EntityQuery.from("Corporations").where("Name", "startsWith", "Zen");
manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
var ib = $("#infoBox")[0];
ko.applyBindings(data, ib);
}
});
Html
<div id="infoBox"">
<ul data-bind="foreach: results">
<li>
<strong><span data-bind="text:City"></span></strong>
<span data-bind="text:Name"></span>
</li>
</ul>
</div>
You should not reapply bindings. You should update only data. You should not apply bindings multiple times to the same DOM elements.
Related
Basically I'm trying to make a todo list app similar to Trello. I have a button that when pressed turns into an input element, gets a "To Do Task" item and adds that to a list. This is achieved by this piece of code:
function createCardBoxNode(title){
/*HTML looks like:
<div class="task-card">
<div class="writings">
<p class="title">Tasks To Do</p>
<ul id="tasks">
<li>Task 1</li>
</ul>
</div>
<button class="btn-add-task">
Add New Task...
</button>
<input....>
</div>
*/
var containerBox = createElement('div', {class:'task-card'});
var writingPartBox = createWritingAreaNode(title);
var newTaskBtn = createElement('button', {class:'btn-add-task show'},'Add New Task...');
var newTaskInput = createElement('input', {class:'new-task hide', type:'text', placeholder:'New Task'});
//When 'add new task' is clicked, make it an input area
newTaskBtn.addEventListener('click', function(){
newTaskBtn.classList.remove('show');
newTaskBtn.classList.add('hide');
newTaskInput.classList.remove('hide');
newTaskInput.classList.add('show');
newTaskInput.focus();
});
// when input is entered, that's a new "To Do Task" so add it to the list
newTaskInput.addEventListener('keyup', function(e){
if (e.keyCode === 13){
// If Enter is pressed
var newTask = createListItems(newTaskInput.value);
var listArea = document.getElementById('tasks');
listArea.appendChild(newTask);
newTaskInput.classList.remove('show');
newTaskInput.classList.add('hide');
newTaskInput.value = '';
newTaskBtn.classList.remove('hide');
newTaskBtn.classList.add('show');
}
});
containerBox.appendChild(writingPartBox);
containerBox.appendChild(newTaskBtn);
containerBox.appendChild(newTaskInput);
return containerBox;
}
This works fine until I add another Card at the same time and decide to add new tasks to the second card. Then every task gets added to the first card. I wonder if there is any way to check if the "input" that's being sent is going to a specific card checking the card's title. I don't have any limits on how many tasks can be added to each card, and don't want to add that. I also want the user to be able to work on two separate cards at the same time. As a beginner, I also want to fix this using only JavaScript. I hope I've explained the issue well enough.
Edit:
I have tried doing this:
if (document.querySelector('.title').innerText === title){
var newTask = createListItems(newTaskInput.value);
var listArea = document.getElementById('tasks');
listArea.appendChild(newTask);
newTaskInput.classList.remove('show');
newTaskInput.classList.add('hide');
newTaskInput.value = '';
newTaskBtn.classList.remove('hide');
newTaskBtn.classList.add('show');
But then I cannot add anything new to the second box.
I think your main problem is that you use the <ul> with the same id for different cards.
First of all, change your markup and replace <ul id="tasks"> with <ul class="tasks-list">
<div class="task-card">
<div class="writings">
<p class="title">Tasks To Do</p>
<ul class="tasks-list"> <!-- !!! here !!! -->
<li>Task 1</li>
</ul>
</div>
<button class="btn-add-task">
Add New Task...
</button>
<input....>
</div>
and then change that selector in your keyup handler:
//...
var listArea = containerBox.querySelector('.tasks-list');
// ...
Also, it would be better to declare
var listArea = containerBox.querySelector('.tasks-list');;
outside your handler.
I solved it on my own.
The trick is to not select anything at all. Everytime you queryselect anything, or get an element using it's ID - it will select the first element with that id or class.
What I ended up doing is to combine two of my functions, have a function generate my and then just straight up append the list items into that ul. No selection whatsoever.
From recursive list of items
<script type="text/ng-template" id="menu_sublevel.html">
id:{{item.id}}
<ul ng-if="item.subs">
<li ng-repeat="item in item.subs" ng-click="openItem(item)" ng-include="'menu_sublevel.html'">
id:{{item.id}}
</li>
</ul>
</script>
<ul>
<li ng-repeat="item in menu.items" ng-click="$event.stopPropagation()" ng-include="'menu_sublevel.html'"></li>
</ul>
and effect
id:0
...id:4
...id:5
...id:16
...id:17
...id:18
...id:6
...id:20
...id:21
...id:22
I want to have selected one at time item.
When i write nested list without recursion i use id and on every level I have method for item selection and i chceck `
levelOneItemSelected.id === item.id
How to select child with id 16 and have his parent with id 5 opened and next parent with id 0 opened while changing selection closes opened items.
If, upon invocation of openItem(item), you also want to select/open its ancestors, then its best to have the reference from item to its parent, for example, item.$$parent. That would enable you to traverse the item's ancestors and modify them. Conceptually speaking, it would look like so:
$scope.openItem(item){
item.isOpen = true;
while (item.$$parent){
item = item.$$parent;
item.isOpen = true;
}
}
So, one way is to pre-process your items and set the .$$parent property accordingly.
If you don't like the idea of changing the item object (could be your domain model), you could always pre-process your domain model and produce a view model that wraps a domain model. It would look like so (in concept):
$scope.menu = [
{ $$parent: null,
item: {id: 0, subs: [
{ $$parent: parentObj, // points to its parent
item: {id: 10, subs: [...]}
}
]}
},
// etc ...
]
But if you don't want to modify either, you could use the fact that ng-repeat creates a child scope and instantiate the $$ancestors property at each scope level. (Notice also, that ng-click should be on the displayed item, not on the <li> for subitems):
<script type="text/ng-template" id="menu_sublevel.html">
<span ng-click="openItem(item, $$ancestors)"
ng-class="{'open': item.isOpen}">id:{{item.id}}</span>
<ul ng-if="item.subs"
ng-init="$$p = $$ancestors.slice(); $$p.push(item)">
<li ng-repeat="item in item.subs"
ng-init="$$ancestors = $$p"
ng-include="'menu_sublevel.html'">
id:{{item.id}}
</li>
</ul>
</script>
<ul>
<li ng-repeat="item in menu.items"
ng-init="$$ancestors = []"
ng-include="'menu_sublevel.html'"></li>
</ul>
Then, in the controller, openItem needs to change:
var currentOpenItem = null,
currentOpenItemAncestors = [];
$scope.openItem = function(item, ancestors){
// closes the currently open item and its ancestors
closeItem(currentOpenItem, currentOpenItemAncestors);
currentOpenItem = item;
currentOpenItemAncestors = ancestors;
openItem(item, ancestors);
}
Demo
The drawback of this approach is that it offloads some of the logic to the View and makes the View more complex and your controller less testable:
I have a <ul> of elements like this:
<ul id="task-list">
<li class="task"></li>
<li class="task"></li>
<li class="task"></li>
<li class="task"></li>
</ul>
Every time a .task is changed, added, or removed, I generate the entire <ul> again through a Handlebars template and display it with .html():
function addHTML(allTasks) {
var tasks = compileTemplate(allTasks);
return $('#task-list').html(tasks);
}
function compileTemplate(allTasks) {
var source = $('#task-template').html();
var template = Handlebars.compile(source);
return template({
tasks: allTasks
});
}
The list uses Slip.js for reordering, and new items are added at the top of the list. The height of each item could be anything.
Is it possible to have a CSS animation for new additions where the list "slides down" to pop in the new task, or will I need to change my logic to add each task to an existing list, instead of generating the whole <ul> each time?
You can solve this with some simple jquery functions, although AngularJS would look much cleaner. A little example for adding and removing elements with JSFiddle example.
HTML
<ul id="task-list">
<li class="task"><button class="remove">remove</button>Lorem</li>
<li class="task"><button class="remove">remove</button>Ipsum</li>
<li class="task"><button class="remove">remove</button>Dilor</li>
<li class="task"><button class="remove">remove</button>Test</li>
</ul>
<li class="task" id="hidden-task"><button class="remove">remove</button>Test</li>
<button class="add">add last Child</button>
jQuery
//.remove is class of your remove button, task is the list element
$('.remove', '.task').on('click', function(){
var li = $(this).parent();
li.slideToggle({
'complete':function(){
li.remove()
}
});
})
//use hidden element as dummy
var element = $('#hidden-task');
//cache tasklist
var taskList = $('#task-list');
$('.add').on('click', function(){
//create a deep clone
var clone = element.clone(1,1);
taskList.append(clone);
setTimeout(function(){
clone.slideToggle()
}, 200);
});
var nth = 2;
$('.c2th').on('click', function(){
var clone = element.clone(1,1);
$('li').eq(nth-1).after(clone);
setTimeout(function(){
clone.slideToggle()
}, 200);
});
In bootstrap, the standard way of indicating that an item is selected is via the .active class. I have a list group that is created via a foreach knockout.js structure.
<div class="list-group" data-bind="foreach: people">
<a href="#" class="list-group-item" data-bind="click: $root.personSelected, css: {active: $root.chosenPerson.name == $data.name}">
<h4 class="list-group-item-heading" data-bind="text: name"></h4>
</a>
<div>
When a person is selected, my call to personSelected sets the selected object to a observable personSelected. I figured that I could use another data binding of the form css: {active: $root.chosenPerson.name == $data.name} to check if the current item was selected, but this does not appear to work. See the jsFiddle
I think that I might not be using the correct comparison statement, or perhaps there is a better way to do this. Any thoughts?
Thanks!
You are on the right track with the css binding and combining it with observables.
But first you need to fix your personSelected function to correctly set your chosenPerson observable (observable are functions you need to call them with the new value as the argument):
self.personSelected = function(person){
self.chosenPerson(person);
}
Then I would create a new helper function (you can shovel all this logic into the binding expression but this is not a good practice) which based on the name returns whether this person is the selected:
self.isSelected = function(name) {
var selectedperson = self.chosenPerson()
if(selectedperson) //handle if no person is selected
{
return selectedperson.name == name;
}
}
Then you just need to use this function in your binding:
<a href="#" class="list-group-item"
data-bind="click: $root.personSelected,
css: { active: $parent.isSelected(name) }">
Demo JSFiddle.
The problem is you weren't setting the value of chosen person observable, you were setting chosen person equal to something new. Also, when your view model is instantiated, you didn't have a value of chosen person yet defined to test against.
http://jsfiddle.net/x52VL/1/
When you set chosenPerson do this -
self.chosenPerson(person);
and test against it like this -
<a href="#" class="list-group-item" data-bind="click: $parent.personSelected, css: {active: $parent.chosenPerson().name == name}">
How can I move an element to different places in the DOM with angular js?
I have a list of elements like so
<ul id="list" ng-controller="ListController">
<li ng-controller="ItemController"><div>content</div></li>
<li ng-controller="ItemController"><div>content</div></li>
<li ng-controller="ItemController"><div>content</div></li>
<li ng-controller="ItemController">
<div>content</div>
<div id="overlay"></div>
</li>
</ul>
What I'm trying to accomplish is moving the #overlay from place to place within the list without having to have a hidden duplicate in every item that I flag hidden/unhidden.
If this was jquery I could just do something like this:
$("#overlay").appendTo("#list li:first-child");
Is there an equivalent way to do this in angular?
Thanks to your clarifications I can understand that you've got a list of items. You would like to be able to select one item in this list (swipe but potentially other events as well) and then display an additional DOM element (div) for a selected item. If the other item was selected it should be un-selected - this way only one item should have an additional div displayed.
If the above understanding is correct, then you could solve this with the simple ng-repeat and ng-show directives like this:
<ul ng-controller="ListController">
<li ng-repeat="item in items">
<div ng-click="open(item)">{{item.content}}</div>
<div ng-show="isOpen(item)">overlay: tweet, share, pin</div>
</li>
</ul>
where the code in the controller would be (showing a fragment of it only):
$scope.open = function(item){
if ($scope.isOpen(item)){
$scope.opened = undefined;
} else {
$scope.opened = item;
}
};
$scope.isOpen = function(item){
return $scope.opened === item;
};
Here is the complete jsFiddle: http://jsfiddle.net/pkozlowski_opensource/65Cxv/7/
If you are concerned about having too many DOM elements you could achieve the same using ng-switch directive:
<ul ng-controller="ListController">
<li ng-repeat="item in items">
<div ng-click="open(item)">{{item.content}}</div>
<ng-switch on="isOpen(item)">
<div ng-switch-when="true">overlay: tweet, share, pin</div>
</ng-switch>
</li>
</ul>
Here is the jsFiddle: http://jsfiddle.net/pkozlowski_opensource/bBtH3/2/
As an exercise for the reader (me), I wanted to try a custom directive to accomplish this. Here is what I came up with (after many failed attempts):
<ul ng-controller="ListController">
<li ng-repeat="item in items">
<div singleton-overlay>{{item.content}}</div>
</li>
</ul>
A service is required to store the element that currently has the overlay, if any. (I decided against using the controller for this, since I think a 'service + directive' would make for a more reusable component than a 'controller + directive'.)
service('singletonOverlayService', function() {
this.overlayElement = undefined;
})
And the directive:
directive('singletonOverlay', function(singletonOverlayService) {
return {
link: function(scope, element, attrs) {
element.bind('click', moveOrToggleOverlay);
function moveOrToggleOverlay() {
if (singletonOverlayService.overlayElement === element) {
angular.element(element.children()).remove();
singletonOverlayService.overlayElement = undefined;
} else {
if (singletonOverlayService.overlayElement != undefined) {
// this is a bit odd... modifying DOM elsewhere
angular.element(singletonOverlayService.overlayElement.children()).remove();
}
element.append('<div>overlay: tweet, share, pin</div>')
singletonOverlayService.overlayElement = element;
jsFiddle: http://jsfiddle.net/mrajcok/ya4De/
I think the implementation is a bit unconventional, though... the directive not only modifies the DOM associated with its own element, but it may also modify the DOM associated with the element that currently has the overlay.
I tried setting up $watches on scope and having the singleton store and modify scope objects, but I couldn't get the $watches to fire when I changed the scope from inside the moveOrToggleOverlay function.