I have an observable array that I add or remove elements to. The elements are displayed as DIVs.
I want to make each DIV draggable, however because the DIV is created on the fly I am not sure how to do this. I was thinking of using the JQuery live() function, but I need to pass and action, so I dont think this would be the right approach.
This is my code:
Knockout:
function AssetViewModel() {
var self = this;
self.assets = ko.observableArray([]);
self.addAsset = function(){
self.assets.push(
{
id: "1",
content: "Hello World",
type: "Asset"
}
);
}
self.removeAsset = function(asset){
self.assets.remove(asset);
};
};
HTML:
<div id="layer1" data-bind="foreach: assets">
<div data-bind="text: content" class="asset"></div>
</div>
Any suggestions would be greatly appreciated!
This is a job for custom bindings. The gist is you create your own binding so you markup looks like this:
<div data-bind="foreach: assets">
<div data-bind="draggable: $data">
<p>More markup</p>
</div>
</div>
The custom binding would be:
ko.bindingHandlers.draggable = {
init: function (element, valueAccessor, allBindingsAccessor, vieModel, bindingContext){
$(element).draggable();
return ko.bindingHandlers.with.init.apply(this, arguments);
}
};
You don't need to return anything but by calling the with binding init function, you create a wrapper binding that performs your logic and and acts like the binding you're returning. It's generally a good place to start when learning custom bindings.
Try to read next article - Revisiting Dragging, Dropping, and Sorting observableArrays
It contains detailed explanation and number of samples.
I'm unsure whether you just want to add sorting within your layer1 div (or dragging and dropping amongst other dom elements.) Regardless of what you want I'd use the Knockout-Sortable plugin (https://github.com/rniemeyer/knockout-sortable).
It worked for me on a complex sort, drag and drop solution with Knockout (named templates nested multiple layers.)
Check the JSFiddles at the bottom of that page to get started.
Hope that helps.
Related
how can I create in an AngularJS directive some DOM elements and set on them a click event? In my directive I create my elements in this way:
var list = document.createElement("div");
list.classList.add('myList');
for(var i = 0; i < n; i++) {
var item = document.createElement("div");
item.classList.add('myItem');
list.appendChild(item);
}
so I have an external div container that contains some div elements.
This is my generated HTML:
<div class="myList">
<div class="myItem">
<div class="myItem"></div>
<div class="myItem"></div>
<div class="myItem">
</div>
In the same directive I have to set a click event on those elements, in jQuery I can do:
$(".myItem" ).on( "click", function() {
// Do something
});
I try to that in Angular in many ways but I have problems to set the on click event:
var list = document.querySelector('.myList');
_.forEach(list.children, function(value, index){
var item = document.querySelector(value);
item.bind("click",function(){
// Do something
});
});
I get an error:
Failed to execute 'querySelector' on 'Document': '[object HTMLDivElement]' is not a valid selector.
Also, if I want get all myItem directly (without list.children) I write use:
var item = document.querySelector('.myItem');
I get:
item.bind is not a function (caused by "undefined")
I can set an ng-click in the directive... how?
item.on( "click", function() {
// Do something
});
If I use .on() method it's undefined like .bind().
Anyone can help me? Thanks in advice :)
I believe what you need is
<div class="myList">
<div class="myItem" ng-click="yourClickFn()">
<div class="myItem" ng-click="yourClickFn()"></div>
<div class="myItem" ng-click="yourClickFn()"></div>
<div class="myItem" ng-click="yourClickFn()">
Then in your Angular controller:
$scope.yourClickFn = function(){
//the code you want to execute here
}
Do you have a real reason to create your elements this way? It's really not the angular-way of doing things. You should use an ng-repeat on to create your html.
Also if you have a chance to update to anguar 1.5+ you could use components instead of directives, this would make your life easier.
Update
Alternatively you could do it with jQuery after the elements are created actually. I think it's easier to read than the plain js version.
Put a class on them and do something like:
$('.myClass').on('click', function() {
//your code
if (!$scope.$$phase) { // this checks whether you are already in a digest cycle or not - you probably won't be at this phase.
$scope.$apply(); // this will update the html if you did something to the model above this if
}
});
If you have a directive put this code to the link function, if you have a component then put it into the $postLink lifecycle hook (works after 1.5.3 I think) as these functions are called after your html has been already generated.
Usually these are the places for "messy" or "non-angular" code ^_^
I'm pretty new to Meteor and I'm struggling with arkham:comments-ui.
Here's a brief overview of what I'm doing. I hope it's not too detailed.
I'm subscribed to a published collection called 'articles'.
Articles = new Mongo.Collection("articles");
I'm iterating through this on load to display a preview of each article in the client via a template named 'articlepreview'.
Template.body.helpers({
articles: function(){
return Articles.find({}, {sort: {createdAt: -1}});
}
});
I've added the unique id of each article to a custom attribute (data-id) of the 'read more' button at the bottom of each preview.
<template name="articlepreview">
<h2>{{head}}</h2>
<p>{{{summary}}}</p>
<p class="readmore" data-id="{{_id}}">Read more</p>
</template>
I've added an event listener for this button which gets the custom attribute and calls a function to display the full article that was clicked as well as setting a Session variable with the unique id.
"click .readmore": function (event) {
event.preventDefault();
var articleID = event.target.getAttribute("data-id");
Session.set('articleUID',articleID);
Meteor.call("loadArticle", articleID);
}
This function populates a template named 'articlefull' via a helper; essentially I use Session.set to set a variable containing the text of the full article by using findOne with the unique ID that has been set.
HELPER:
Template.articlefull.helpers({
articlebody: function () {
return Session.get('articlebody');
},
uniqueid: function(){
return Session.get('articleUID');
}
});
TEMPLATE:
<template name="articlefull">
<div id="article">
<p>{{{articlebody}}}</p>
</div>
<div id="comments" class="comment-section">
{{> commentsBox id=uniqueid}}
</div>
</template>
Part of this template is a comment box. I'm setting the id of the comment box to match that of the article loaded, but something really odd happens at this point: the comments box allows me to type a comment and click 'Add', but once I do the comment briefly flashes on the screen and then disappears.
Any thoughts on what I'm doing wrong? If I pop {{uniqueid}} into the template just below the comment box it displays the right value, which means it is getting pulled through, but something is still going wrong...
PS: Please also tell me if I'm going about this in totally the wrong way. I'm sure there's a simpler way to do what I'm trying to but as I said, I'm new to this. Thanks!
Based on your detailed description about your implementation, I assume that this issue occurs due to a missing publish and subscribe function for your Comments collection.
Depending on your use case, you'll need to add Meteor.publish(name, func) and Meteor.subscribe(name, [arg1, arg2...], [callbacks]) functions.
Meteor.publish('allComments', function() {
return Comments.getAll();
});
Meteor.publish('articleComments', function(docId) {
return Comments.get(docId);
});
Meteor.subscribe('allComments');
Meteor.subscribe('articleComments', Session.get('articleUID'));
Im new to knockoutJS and really loving it. I'm trying to build something very similar to this http://jsfiddle.net/mac2000/N2zNk/light/. I tried copying the code and adapting it to my need. The problem with that is that I get my data from the server using $.getJSON it seems that the jsfiddle example was made for a different format of data which just confuses me.
So instead of asking for help to find the issue with my code I rather take a different approach. Hopefully you guys wont mind. Im starting from scratch and trying to learn each steps so I know what im doing.
Here is my code so far, this works great to simply display my data on my table.
<script type="text/javascript">
function EmployeeModal() {
var self = this;
self.Employees = ko.observableArray([]);
$.getJSON("../../../../_vti_bin/listData.svc/GDI_PROD_Incidents?$filter=ÉtatValue%20ne%20%27Fermé%27&$orderby=PrioritéValue desc",
function (data) {
if (data.d.results) {
self.Employees(ko.toJS(data.d.results));
}
}
);
}
$(document).ready(function () {
ko.applyBindings(new EmployeeModal());
});
</script>
I made a template where each row has an edit button similar to the example but no fucntion of binding are done yet. Now what I would like to do is simply onlick pass the selected data to my modal and show my modal like so:
$('#myModal').modal('show');
This is the step im struggling the most with. Would any have any clear documentations for a noob or example, hints or any type of help I would take to get me in the right direction from here.
Assume you have them in a list like this:
<ul data-bind="foreach: Employees">
<li data-bind="text: fullName, click: showEmployee"/>
</ul>
What I'd recommend is to update your view model to look like this:
function EmployeeModal() {
var self = this;
self.Employees = ko.observableArray([]);
self.currentEmployee = ko.observable(null);
self.showEmployee = function(vm){
self.currentEmployee(vm);
$('#myModal').modal('show');
};
.... // rest of your view model here
}
The last piece will be using KO's with binding to wrap your modal
<div class="modal" id="myModal" data-bind="with: currentEmployee">
<h1 data-bind="text: fullName"></h1>
</div>
What this does is listen for the click event on an individual element and automatically pass the view model bound to that element to the click handler you defined. We're then taking that view model, storing it in its own observable and then showing the modal like normal. The key here is the with binding which only executes when there's data stored in the observable, and also sets the context of all nested code to be the employee stored in currentEmployee.
There's a lot there if you're new, but using a current style observable to track individual data from a list is a great paradigm to use with Knockout = )
P.S.. the http://learn.knockoutjs.com/ tutorials are really great to work through if you've yet to do so.
I'm trying to specify an entrance effect on elements being inserted using a knockoutjs foreach binding. Very simple setup:
myViewModel.myObservableArray.push({enter:function() { ... });
and in the markup:
foreach:{data:myObservableArray, afterRender:enter}
seems like it should work... right? But it doesn't find the enter function on the item. What I've found does work is:
myViewModel.enter = function(something, item) { item.enter(); };
foreach:{data:myObservableArray, afterRender:$root.enter}
adding an enter function to the root view model and binding afterRender to $root.enter. Enter is then passed the item as its second param so can in turn call the item's enter function, but it feels like a hack.
Can anyone explain what's going on here?
Thanks.
EDIT:
To clarify I've created a fiddle.
What this does is very simple, and is covered in more depth in the animated transitions example. It's running a function in the root view model for each dom element that's inserted using the foreach binding.
So the question is: what if I want item specific afterRender, afterAdd or beforeRemove functions? I could see this being useful. Especially if using the template binding to dynamically select a template (note 4). Is there a clean way of doing this? Right now I've got an enter function in the view model's root that simply calls the enter function on the item, but like I said above this feels like a hack.
Nope, this is the way it was designed.
From the Documenation:
Note 3: Using “afterRender”, “afterAdd”, and “beforeRemove”
Sometimes you might want to run custom post-processing logic on the DOM elements generated by your templates. For example, if you’re using a JavaScript widgets library such as jQuery UI, you might want to intercept your templates’ output so that you can run jQuery UI commands on it to transform some of the rendered elements into date pickers, sliders, or anything else.
Generally, the best way to perform such post-processing on DOM elements is to write a custom binding, but if you really just want to access the raw DOM elements emitted by a template, you can use afterRender.
Pass a function reference (either a function literal, or give the name of a function on your view model), and Knockout will invoke it immediately after rendering or re-rendering your template.
(Emphasis mine)
As it says, a custom binding is another way to do it, and may be better depending on what that enter() function does.
underscore debounce (_.debounce) is a great solution in such case.
template
data-bind=" template: {foreach:myObservableArray, afterRender: runWhenAllRenderDone }
debounce function will be executed if afterRender is not fired in last 100 milisecond.
var runWhenAllRenderDone = _.debounce(myFunction, 100);
function myFunction(){
//do some task but do it for once only
}
is't it awesome?
Found another workaround without timeout, this technique is based on virtual element <!-- ko if: $parent.afterRender($index()) --><!-- /ko -->
function ReservationsViewModel() {
// Editable data
this.seats = ko.observableArray([
{ name: "Steve", meal: "Standard (sandwich)", price: 343},
{ name: "Steve", meal: "Premium (lobster)", price: 10},
{ name: "Steve", meal: "Ultimate (whole zebra)", price: 290}
]);
this.afterRender = (i) => {
// checking element rendered is last
if (i === this.seats().length - 1) {
console.log('rendered');
// our after rendered logic goes here...
}
};
}
And it's template is
<tbody data-bind="foreach: seats">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: meal"></td>
<td data-bind="text: price"></td>
</tr>
<!-- ko if: $parent.afterRender($index()) --><!-- /ko -->
</tbody>
This extra logic i === this.seats().length - 1, will check last row is rendered.. then we can execute our afterRender logic inside.
I have an issue where knockout.js 2.0 isn't showing my item when a CSS style is applied to it. It won't update the display with the style applied to it. If it is off it works.
CSS
.success { display:none }
HTML
<div data-bind="visible: site.signUp.success()" class="success">
Thanks for signining up. You will recieve an email from us in the near future.
</div>
JS
app.viewModel.site.signUp.success(true);
In the period of time before Knockout.js applies bindings, you can prevent the initial rendering/flashing effect by setting the default display style to none.
<div style="display: none" data-bind="visible: site.signUp.success">
Thanks for signining up. You will recieve an email from us in the near future.
</div>
I created a fiddle that shows how you can use the css binding in Knockout to do this. http://jsfiddle.net/johnpapa/vwcfT/
Here is the HTML:
Success Flag: <input type="checkbox" data-bind="checked:site.signUp.success"></input>
<div data-bind="visible: site.signUp.success" >
Thanks for signining up. You will recieve an email from us in the near future.
</div>
<br/><br/>
<span data-bind="text:site.signUp.success"></span>
<div data-bind="css: { success: site.signUp.success}" >
Thanks for signining up. You will recieve an email from us in the near future.
</div>
The first DIV in the example just uses the visible binding, since you dont really need a css class to do this. The second DIV in the example binds to a css class named "success" if the site.signUp.success observable is true. This is more verbose than the first, but could be useful if you needed your css class to do more than just set visibility.
Hope this helps.
Here is the javascript:
var viewModel = {
site: {
signUp: {
success: ko.observable(true)
}
}
};
ko.applyBindings(viewModel);
That's because the success style is defined as display:none, which is equivalent to visible = false. Your CSS class is cancelling out your site.signUp.success() call.
If you want your DIV to show up only when site.signUp.success() == true, just do this:
<div data-bind="visible: site.signUp.success">
Thanks for signining up. You will recieve an email from us in the near future.
</div>
It might be a bit late but I found the following useful. Instead of fixing every element with a visibility control, just wrap a div around all your pre-hidden elements as follow:
<div style="display:none" data-bind="visible: true">
Some pre-hidden elements
<div data-bind="visible: myVisibleFoo">...</div>
<div data-bind="visible: myVisibleBar">...</div>
Other pre-hidden elements
...
</div>
The whole section of elements is hidden initially and is only shown after KO has applied bindings. I usually wrap the whole page with it to avoid any flashing problem.
Just run into this myself; I can see why the did it this way, but it is handy to set a default visibility of none on late loaded elements on the page so they don't flash as scripts are loaded. The nicest way I could find of doing this was just to create a simple custom binding:
ko.bindingHandlers.forceVisible = {
update:
function(el, f_valueaccessor, allbindings, viewmodel, bindingcontext)
{
if(ko.unwrap(f_valueaccessor()))
el.style.display = 'inherit';
else
el.style.display = 'none';
}
};
You have to be a little bit careful when setting up your styles; if you are using a div and your CSS sets it to display:inline-block then this code will not work - it will have block display when the inherit is applied. This simple solution was fine for my use case, however.