how to trigger JQuery .draggable() on elements created by templates? - meteor

I have a standard template in an Html file like:
<template name="cards">
{{#each all_cards}}
{{> card_item}}
{{/each}}
</template>
<template name="card_item">
<div class="card" style="left:{{position.x}}px; top:{{position.y}}px">
{{title}}
</div>
</template>
I want to have the cards (css selector .card) become draggable with JQuery.
Now since Meteor automagically updates the DOM using the template, when and how do I know where to call .draggable() on what??
EDIT: This is so far my solution which makes pending movements on other client visible with a wobble animation (using CSS3):
Template.card_item.events = {
'mouseover .card': function (e) {
var $target = $(e.target);
var $cardContainer = $target.hasClass('card') ? $target : $target.parents('.card');
$cardContainer.draggable({containment: "parent", distance: 3});
},
'dragstart .card': function (e) {
Session.set("dragging_id", e.target.id);
$(e.target).addClass("drag");
pos = $(e.target).position();
Events.insert({type: "dragstart", id:e.target.id, left: pos.left, top: pos.top});
},
'dragstop .card': function (e) {
pos = $(e.target).position();
Events.insert({type: "dragstop", id:e.target.id, left: pos.left, top: pos.top});
Cards.update(e.target.id, {$set: {left:pos.left, top:pos.top}});
Session.set("dragging_id", null);
}
}
Events.find().observe({
added: function(event) {
if (event.type == "dragstart" && !Session.equals("dragging_id", event.id)) {
$("#"+event.id).draggable({disabled: true});
$("#"+event.id).addClass("wobble");
}
if (event.type == "dragstop" && !Session.equals("dragging_id", event.id)) {
$("#"+event.id).animate({left: event.left, top: event.top}, 250);
Events.remove({id:this.id});
$("#"+event.id).draggable({disabled: false});
}
}
});

EDIT: This approach doesn't seem to work in the latest versions of Meteor, e.g. v0.5.0. See my comment below.
Looks like we're working on similar things! I've got a working proof of concept for a simple Magic: The Gathering app. Here's how I have dragging implemented at the moment:
In a <head> section in one of your html files, include the jQuery UI script:
<script src="jquery-ui-1.8.20.custom.min.js"></script>
Then, in a js file, make sure elements become draggable on mouseover (note: this is sub-optimal on touchscreens since it requires two touches to drag... I'm looking for a better touchscreen solution):
Template.card_item.events['mouseover .card, touchstart .card'] = function (e) {
var $target = $(e.target);
if (!$target.data('isDraggable')) {
$target.data('isDraggable', true).draggable();
}
};
And finally, handle the drag and dragstop events:
var prevDraggedTime = 0
Template.card_item.events['drag .card'] = function (e) {
// get the cardId from e
var now = new Date().getTime();
var position;
if (now - prevDraggedTime > 250) {
position = $(e.target).position();
Cards.update(cardId, {$set: {x: position.top, y: position.left}});
prevDraggedTime = now;
}
}
Template.card_item.events['dragstop .card'] = function (e) {
// get the cardId from e
var position = $(e.target).position();
Cards.update(cardId, {$set: {x: position.top, y: position.left}});
}

Related

Aframe: cycle through colors using array

I've been trying to cycle through colors using a custom component.
<script>
AFRAME.registerComponent('floor-cycle', {
init: function () {
var sceneEl = document.querySelector('a-scene');
var floorEl = sceneEl.querySelector('#floor')
status = 1;
floorEl.addEventListener('click', function () {
if(status==1) {
floorEl.setAttribute('color', 'red'); status = 2
}
else if(status==2) {
floorEl.setAttribute('color', 'blue'); status = 3;
}
else if(status==3) {
floorEl.setAttribute('color', 'green'); status = 1
}
}
);
}
});
</script>
The component uses status to set the color attribute on click event, however this seems inefficient. Can this be implemented using an array rather than status?
demo - https://codepen.io/MannyMeadows/pen/GWzJRB
You can make an array ['red','green','blue'] and Cycle through it:
colors = ['red','green','blue'];
let i = 0;
floorEl.addEventListener('click',function(){
floorEl.setAttribute('material','color', colors[i]);
function add(){(i==colors.length-1) ? i = 0 : i++;}
add();
});
Seems better as the array is now dynamic, not sure how about the performance.
working fiddle here: https://jsfiddle.net/gftruj/g9wfLgab/2/

Infinite scrolling with Meteor

I am trying to load 12 items only each time, until the user scroll all the way down and load another 12 elements
For some reason my code doesn't work. When i upload another item, i can see it in the admin panel so it is successfully uploaded but i can't see it in the normal user view. i can only view the first 12 items uploaded and it doesn't load anymore items when i scroll.
Here is my code in the client side
if (Meteor.isClient) {
var ITEMS_INCREMENT = 12; //this one refers to the number of elements to load
Session.setDefault('itemsLimit', ITEMS_INCREMENT);
Deps.autorun(function() {
Meteor.subscribe('items', Session.get('itemsLimit'));
});
Template.list_products.helpers({
applications: function () {
var limit = Session.get("itemsLimit");
//return Products.find({}, { sort: {createdAt: -1},limit: limit }); // render latest first
return Products.find({}, { sort: {createdAt: 1},limit: limit }); // render first first
}
});
Template.list_products.moreResults = function() {
// If, once the subscription is ready, we have less rows than we
// asked for, we've got all the rows in the collection.
return Products.find({}, { sort: {createdAt: -1},limit: limit });
}
// whenever #showMoreResults becomes visible, retrieve more results
function showMoreVisible() {
var threshold, target = $("#showMoreResults");
if (!target.length) return;
threshold = $(window).scrollTop() + $(window).height() - target.height();
if (target.offset().top < threshold) {
if (!target.data("visible")) {
// console.log("target became visible (inside viewable area)");
target.data("visible", true);
Session.set("itemsLimit",
Session.get("itemsLimit") + ITEMS_INCREMENT);
}
} else {
if (target.data("visible")) {
// console.log("target became invisible (below viewable arae)");
target.data("visible", false);
}
}
}
// The below line is to run the above func every time the user scrolls
$(window).scroll(showMoreVisible);
}
Here how i solved it:
if(Meteor.isClient) {
Session.set("itemsLimit", 9); // to set the limit to 9
lastScrollTop = 0;
$(window).scroll(function(event){
if($(window).scrollTop() + $(window).height() > $(document).height() - 100) { // to detect scroll event
var scrollTop = $(this).scrollTop();
if(scrollTop > lastScrollTop){ // detect scroll down
Session.set("itemsLimit", Session.get("itemsLimit") + 9); // when it reaches the end, add another 9 elements
}
lastScrollTop = scrollTop;
}
});
}
It works like a charm now :)
You can implement it like this:
HTML File:
<template name="yourTemplateName">
<div id="divId">
{{#each dataArr}}
//your view here.
{{/each}}
</div>
{{#if noMoreItem}}
<span>No more items to show</span>
{{/if}}
</template>
JS File:
var pageNumber = new ReactiveVar(0);
var noMoreItem = new ReactiveVar(false);
var mainContainer = // Your element here example: document.getElementById('divId')
mainContainer.addEventListener('scroll', function(){
if(mainContainer.scrollHeight - mainContainer.scrollTop === mainContainer.clientHeight) {
getMoreItems();
}
});
var getMoreItems = function () {
if(pageNumber.get() < Math.floor(Counts.get('countItems')/12)) {
pageNumber.set(Number(pageNumber.get())+1);
Meteor.subscribe('pubName', pageNumber.get(), 12);
} else {
noMoreItem.set(true);
}
}
Template.yourTemplateName.rendered = function () {
pageNumber.set(0);
Meteor.subscribe('pubName', pageNumber.get(), 12);
}
Template.yourTemplateName.helpers({
'dataArr': function () {
return CollectionName.find();
},
'noMoreItem': function () {
return noMoreItem.get();
}
})
Publication:
Meteor.publish("pubName", function (pageNumber, pageSize) {
Counts.publish(this, 'countItems', Meteor.users.find(filter), {
noReady: true
});
return CollectionName.find({}, {
skip: pageNumber > 0 ? ((pageNumber) * pageSize) : 0,
limit: pageSize
})
});

Meteor.renderList alway end up in [elseFunc]

I'm new to Meteor.
Trying to render items from collection but Meteor.renderList(observable, docFunc, [elseFunc]) alway go to elseFunc.
this.ComponentViewOrdersFlow = Backbone.View.extend({
template: null,
initialize: function() {
var frag;
Template.ordersFlow.events = {
"click a": function(e) {
return App.router.aReplace(e);
}
};
this.template = Meteor.render(function() {
return Template.ordersFlow();
});
console.log(Colors);
frag = Meteor.renderList(
Colors.find(),
function(color) {
console.log(color);
},
function() {
console.log('else consdition');
}
);
},
render: function() {
this.$el.html(this.template);
return this;
}
});
Initially I thought that Collection is empty, but console.log(Colors) shows that there are items in collection. Moreover if I use Meteor.render(... -> Template.colors({colors: Colors.find()}) ) it renders template end show Collection items there.
Meteor version 0.6.6.3 (Windows 7, 64bit)
Mongo - connected to MongoLab
Thank you for any help.
Jev.
Can't really explain this well in the comments, so here is a very, very simple example of using the Meteor template engine. This is a 100% functional app, showcasing basic reactivity. Note that I never call render() or renderList() anywhere.
All this app does is show a button, that when clicked, adds a number to a list. The number is reactively added to the list, even though I never do anything to make that reactivity explicit. Meteor's templates are automatically reactive! Try it out - this is all of the code.
numbers.html:
<body>
{{> numberList}}
</body>
<template name="numberList">
<ul>
{{#each numbers}}
<li>{{number}}</li>
{{/each}}
</ul>
<button>Click Me</button>
</template>
numbers.js:
var Numbers = new Meteor.Collection("numbers");
if (Meteor.isClient) {
Template.numberList.numbers = function() {
return Numbers.find();
};
var idx = 0;
Template.numberList.events({
"click button": function() {
Numbers.insert({
number: idx
});
idx++;
}
});
}

Deep link to a position in a page, using Meteor JS

I have a meteor app with multiple pages. I want to be able to deeplink to an anchor somewhere halfway the page.
Traditionally, in normal html, you'd make an somewhere in your page, and link to it via /mypage.html#chapter5.
If I do this, my meteor app won't scroll down to that spot.
What is the best approach around this?
#Akshat 's answer works for on the same page, but what if you want to be able to pass around a url w/ a "#" in it? I did it how the meteor docs did.
Template.myTemplate.rendered = function() {
var hash = document.location.hash.substr(1);
if (hash && !Template.myTemplate.scrolled) {
var scroller = function() {
return $("html, body").stop();
};
Meteor.setTimeout(function() {
var elem = $('#'+hash);
if (elem.length) {
scroller().scrollTop(elem.offset().top);
// Guard against scrolling again w/ reactive changes
Template.myTemplate.scrolled = true;
}
},
0);
}
};
Template.myTemplate.destroyed = function() {
delete Template.myTemplate.scrolled;
};
Stolen from the source to the meteor docs.
Are you using some kind of javascript router? Meteor Router?
You could use something like a javascript based scrolling method. One such example is with JQuery: (You can place this in your link/buttons click handler)
Template.hello.events({
'click #theitemtoclick':function(e,tmpl) {
e.preventDefault();
$('html, body').animate({
scrollTop: $("#item_id").offset().top
}, 600);
}
});
Then tag your html item where you would put your anchor with the id:
<h1 id="item_id">Section X</h1>
Currently, there's an issue in IronRouter where the hash is removed from the url. This is discussed here and here. Fortunately there is a fix even though it doesn't appear to be in the stable version.
My Iron Router solution with traditional anchor tags:
1) Apply the IronRouter fix above
2)
Router.configure({
...
after: function () {
Session.set('hash', this.params.hash);
},
...
});
3)
function anchorScroll () {
Deps.autorun(function (){
var hash = Session.get('hash');
if (hash) {
var offset = $('a[name="'+hash+'"]').offset();
if (offset){
$('html, body').animate({scrollTop: offset.top},400);
}
}
Session.set('hash', '');
});
}
Template.MYTEMPLATE.rendered = function (){
anchorScroll();
};
Unfortunately this has to be set in each template's .rendered() otherwise the anchor tag is not guaranteed to be in the DOM.
For better or worse this will scroll again with a code push.
Mike's Answer didn't quite work for me. The hash was returning empty in the onRendered callback. I nested the code in an additional Meteor.setTimeout
fyi I'm using Blaze.
Below worked like a charm :)
Template.myTemplate.onRendered(function() {
Meteor.setTimeout(function(){
var hash = document.location.hash.substr(1);
if (hash && !Template.myTemplate.scrolled) {
var scroller = function() {
return $("html, body").stop();
};
Meteor.setTimeout(function() {
var elem = $("a[name='" + hash + "']");
if (elem.length) {
scroller().scrollTop(elem.offset().top);
// Guard against scrolling again w/ reactive changes
Template.myTemplate.scrolled = true;
}
},
0);
}
},0);
});
Template.myTemplate.destroyed = function() {
delete Template.myTemplate.scrolled;
};

how to concisely write this javascript to show/hide a list of elements?

How to write this type of code in loop? Actually I don't want to write the same same line again and again, Is their any way to compress this code? can we write this code in loop?
function showCandidates()
{document.getElementById("cand9").style.display="block";
document.getElementById("cand10").style.display="block";
document.getElementById("cand11").style.display="block";
document.getElementById("cand12").style.display="block";
document.getElementById("cand13").style.display="block";
document.getElementById("cand14").style.display="block";
document.getElementById("cand15").style.display="block";
document.getElementById("hide_cand").style.display="block";
document.getElementById("view_cand").style.display="none";
}
function hideCandidates()
{document.getElementById("cand9").style.display="none";
document.getElementById("cand10").style.display="none";
document.getElementById("cand11").style.display="none";
document.getElementById("cand12").style.display="none";
document.getElementById("cand13").style.display="none";
document.getElementById("cand14").style.display="none";
document.getElementById("cand15").style.display="none";
document.getElementById("hide_cand").style.display="none";
document.getElementById("view_cand").style.display="block";
}
I suggest this way:
var show_ids = ["cand9", "cand10", "cand11"] // ... and so on
funciton showCandidates() {
for (var index in show_ids) {
var id = show_ids[index];
document.getElementById(id).style.display="none";
}
}
similar for hideCandidates
You should assign to your html elements a class for example
<div class="hideable" >content </div>
Then either you use JQuery or plain javascript to get all the elements that have the "hideable class attribute:
document.getElementsByClassName('hideable')
or
>$(".hideable")
Since your the two previous methods will return an array, you will have to loop through the array and apply the appropriate style attribute.
Firstly, this can be all encapsulated into one function. The function can take a parameter to assign to the display property. And obviously use some if statement in there to deal with the view_cand elements' display.
I would look into using jquery for this though, it makes selecting DOM elements (especially sets of DOM elements) a damn site easier.
I'd write the code for you here but I don't know anything about the elements you're selecting or the structure to your DOM.
Something like this?
for(i=0;i<candNumber;i++){
id= "cand" + i;
document.getElementById(id).style.display="block";
}
Try this .It'll hide/show ( the wayas you requested) by parameter given to function.
setVisibilityByClass("visible"/"invisible") - shows/hides by changing class
setVisibility("block"/"none") - shows/hides by changing styles directly
CHOOSE ONLY ONE.
css classes:
.vissible{ display: block; } .invissible{ display: none; }
Js functions:
function setVisibility(val) {
var not = new Array;
not["none"] = "block";
not["block"] = "none";
for (i = 9; i <= 15; i++){
document.getElementById("cand" + i).style.display = val;
}
document.getElementById("hide_cand").style.display = val;
document.getElementById("view_cand").style.display = not[val];
}
function setVisibilityByClass(val) {
var not = new Array;
not["invissible"] = "vissible";
not["vissible"] = "invissible";
for (i = 9; i <= 15; i++){
document.getElementById("cand" + i).setAttribute("class", val);
}
document.getElementById("hide_cand").setAttribute("class", val);
document.getElementById("view_cand").setAttribute("class", not[val]);
}
I hope this helps:
(function() {
"use strict";
var candidates = {
idx: 0,
getElement: function(id) { return document.getElementById(id); },
toggle: function(elmnts, obj) {
var idx = candidates.idx,
getElement = function(id) { return candidates.getElement(id); };
if (elmnts.length) {
while ( idx < elmnts.length ) {
getElement(elmnts[idx]).style.display = obj.display;
idx++;
}
}
}
};
var idsToHide = [
"cand9", "cand10", "cand11", "cand12",
"cand13", "cand14", "cand15", "hide_cand"
];
var idsToShow = [
"cand9", "cand10", "cand11", "cand12",
"cand13", "cand14", "cand15", "hide_cand"
];
function showCandidates() {
candidates.toggle(idsToShow, {
display: "block"
});
candidates.toggle(["view_cand"], { display: "none" });
}
function hideCandidates() {
candidates.toggle(idsToHide, {
display: "none"
});
candidates.toggle(["view_cand"], { display: "block" });
}
})();
Easy to do with jQuery:
$(document).ready(function(){
$("#candidates").toggle(function (){
$(this).text('Hide Candidates');
$.each($('.candidate'), function() {
$(this).show();
});
}, function() {
$(this).text('Show Candidates');
$.each($('.candidate'), function() {
$(this).hide();
});
});
});
HTML:
Show Candidates
<div class='candidate' id='1'>
<h1>Hello</h1>
</div>
<div class='candidate' id='2'>
<h1>Hello</h1>
</div>
<div class='candidate' id='3'>
<h1>Hello</h1>
</div>
CSS:
.candidate { display: none }
Here's a JS fiddle: http://jsfiddle.net/vbh5T/
If you don't want to use jQuery then please ignore my answer.
(1) First of all, doing these kinds of lookups is best done with jquery. Apart from being easier (see code below), it also allows you pre-calculate the set of elements to act on. This matters, because lookups by ID scan the whole document tree. Accordingly, the more elements in the page, the slower it is to recalculate the set of elements to act on.
(2) Rather than setting individual properties, it is much better to use a css class.
<style>
.invisible {display:none !important;}
</style>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript" charset="utf-8"> // <![CDATA[
$(document).ready(function(){
var hide = function(i) {i.addClass('invisible');};
var show = function(i) {i.removeClass('invisible');};
var candidates = $("#cand9, #cand10 /* etc. [...] */");
/* or, if you rejig this to set a class on all candidate elements:
var candidates = $(".candidate"); */
var hide_cand = $("#hide_cand");
var view_cand = $("#view_cand");
function showCandidates()
{
show(candidates);
show(view_cand);
hide(hide_cand);
}
});
// ]]>
</script>
I leave the corresponding hideCandidates as an exercise for the reader.

Resources