change client data transforms value in meteor.js - d3.js

In my current meteor.js project, user can create a project and add data nodes to it. I'm using D3 to display the nodes in force graph. When they click a particular node from the graph, the corresponding text in the side panel must be highlighted. For this, I need to track with node is selected. But, I don't want to store a "selected" field on the database.
I'm using this data transform to add selected field right now -
/lib/routes.js
Router.route('/project/:code', {
name: 'projectPage',
data: function() {
return {
project: Projects.findOne({code : this.params.code}),
nodes: Nodes.find({project: this.params.code}, {transform: function (doc) {
doc.selected = false;
return doc;
}})
}
}
});
The template is /client/templates/projectPage.html
<template name="projectPage">
<div class="project-page page">
<h3>{{project.title}}</h3>
<p>{{project.summary}}</p>
<div class="work-area">
<div class="map-space">
{{> nodeDisplay nodes=nodes}}
</div>
<div class="type-space">
{{> typeDisplay nodes=nodes}}
</div>
</div>
</div>
</template>
<template name="nodeDisplay">
<div id="svgdiv"></div>
</template>
<template name="typeDisplay">
{{#each nodesData}}
<p>{{text}}</p>
<br/>
{{/each}}
</template>
The click event is handled /client/js/projects.js
Template.nodeDisplay.events({
'click .node':function(event, template){
/*remove previous selection*/
d3.selectAll('.selected circle').attr("r",32);
d3.selectAll('.selected').each(
function(d){
d.fixed = false;
d3.select(this)
.classed('selected', false);
}
);
/*add new selections*/
d3.select(event.currentTarget)
.classed("selected", true)
d3.selectAll('.selected circle').attr("r",40);
var selected_id = $(event.currentTarget).data("id");
Nodes.update(selected_id.toString(), {$set: {selected: true}});
}
});
However, this updates the database to include the "selected" field.
Is there a better way to do this and keep reactivity?

The meteor way is to use session variables and helper functions.
So instead of
Nodes.update(selected_id.toString(), {$set: {selected: true}});
use
Session.set("selected_node", this._id);
and an accompnying helper in Template.typeDisplay.helpers
isNodeSelected: function() {
if(Session.get("selected_node") === this._id) {
return "selected"
}
}
in the template displaying each node (this code assumes that you want to select the corresponding text in the typeDisplay by applying the classname 'selected'):
<template name="typeDisplay">
{{#each nodesData}}
<p class="{{isNodeSelected}}">{{text}}</p>
<br/>
{{/each}}
</template>

Related

Framer motion new animation and exit not working with mapping

for some reason my exit and new animation is not working. I would like new animation to start every time user click on different menu link. I have also tried with " animate='visible'" , and I have tried also to put directly over motion, and it still not doing exit or starting new animation. I am using .map and framer motion together. Can someone please check it out.
This is the code
Thanks
const [forMapping, setForMapping] = useState(wines)
function menuHandler(index, alt) {
setIsActive(index)
if (alt === 'wine') {
setForMapping(wines)
} else if (alt === 'rakia') {
setForMapping(rakia)
}
}
const variants = {
visible: i => ({
y: 0,
transition: {
duration: .7
}
}),
hidden: {
y: '40%'
}
}
<AnimatePresence>
{forMapping.map((item, index) => {
const {
name,
description,
alt,
imageSrc,
price,
glass_price,
iconSrc,
alc
} = item;
return (
<motion.div
exit={{y: '100'}}
viewport={{once: true}}
custom={index}
whileInView='visible'
initial='hidden'
variants={variants}
key={index}
className='item'>
<div className="image">
<Image
width={200}
height={400}
objectFit='cover'
src={imageSrc}
alt={alt}/>
</div>
<div className="info">
<div className="info-header">
<header>
{name}
</header>
<p className="price">
{price.toFixed(2)} EUR
</p>
</div>
<p className="description">
{description}
</p>
<div className="bottom">
<p>
{alc} %VOL
</p>
<div className='image-price'>
<Image
width={18}
height={20}
objectFit='cover'
src={iconSrc}
alt='wine glass'/>
<p className="black">
{glass_price.toFixed(2)} EUR
</p>
</div>
</div>
</div>
</motion.div>
)
})}
</AnimatePresence>
You should not use the loop index as the key in your motion.div. Since the indices (and thus the keys) will always be the same, it doesn't let <AnimatePresence> track when elements have been added or removed in order to animate them.
Instead use a value like a unique id property for each element. This way React (and AnimatePresence) will know when it's rendering a different element (vs the same element with different data). This is what triggers the exit and enter animations.
Here's a more thorough explanation:
react key props and why you shouldn’t be using index

VueJS mouseover in for loop

I have a for that will create a component for each index.
In this component, I have a child div containing edit, add, minus buttons.
I would like it to be displayed on the component mouseover.
How do I achieve this dynamically without having to play with indexes ?
Thank you kindly.
Post component
<template>
<div v-on:mouseleave.native="showOperations = false"
v-on:mouseover.native="showOperations = true">
<!-- post data -->
<div v-if="showOperations">
<!-- operations -->
</div>
</div>
</template>
<script>
export default {
...
data () {
return {
showOperations: false
}
},
...
</script>
List of post
<post v-for="post in posts"
:key="post.id"
:post="post">
</post>
This pattern works for me and I think it works for you as well

Passing the Div id to another vue component in laravel

I created a simple real-time chat application using vue js in laravel.
I am having a problem with the automatic scroll of the div when there is a new data.
What I want is the div to automatically scroll down to the bottom of the div when there is a new data.
Here is my code so far.
Chat.vue file
<template>
<div class="panel-block">
<div class="chat" v-if="chats.length != 0" style="height: 400px;" id="myDiv">
<div v-for="chat in chats" style="overflow: auto;" >
<div class="chat-right" v-if="chat.user_id == userid">
{{ chat.chat }}
</div>
<div class="chat-left" v-else>
{{ chat.chat}}
</div>
</div>
</div>
<div v-else class="no-message">
<br><br><br><br><br>
There are no messages
</div>
<chat-composer v-bind:userid="userid" v-bind:chats="chats" v-bind:adminid="adminid"></chat-composer>
</div>
</template>
<script>
export default {
props: ['chats','userid','adminid'],
}
</script>
ChatComposer.vue file
<template>
<div class="panel-block field">
<div class="input-group">
<input type="text" class="form-control" v-on:keyup.enter="sendChat" v-model="chat">
<span class="input-group-btn">
<button class="btn btn-primary" type="button" v-on:click="sendChat">Send Chat</button>
</span>
</div>
</div>
</template>
<script>
export default{
props: ['chats','userid','adminid'],
data() {
return{
chat: ''
}
},
methods: {
sendChat: function(e) {
if(this.chat != ''){
var data = {
chat: this.chat,
admin_id: this.adminid,
user_id: this.userid
}
this.chat = '';
axios.post('/chat/sendChat', data).then((response) => {
this.chats.push(data)
})
this.scrollToEnd();
}
},
scrollToEnd: function() {
var container = this.$el.querySelector("#myDiv");
container.scrollTop = container.scrollHeight;
}
}
}
</script>
I am passing a div id from the Chat.vue file to the ChatComposer.vue file.
As you can see in the ChatComposer.vue file there is a function called scrollToEnd where in it gets the height of the div id from Chat.vue file.
When the sendchat function is triggered i called the scrollToEnd function.
I guess hes not getting the value from the div id because I am getting an error - Cannot read property 'scrollHeight' of null.
Any help would be appreciated.
Thanks in advance.
the scope of this.$el.querySelector will be limited to only ChatComposer.vue hence child component can not able to access div of parent component #myDiv .
You can trigger event as below in ChatComposer
this.$emit('scroll');
In parent component write ScollToEnd method and use $ref to assign new height
<chat-composer v-bind:userid="userid" v-bind:chats="chats" v-bind:adminid="adminid" #scroll="ScollToEnd"></chat-composer>
..

How to use multiple fineuploader instances with manual upload buttons with one template

With the fine-uploader plugin I am trying to add multiple (dynamic could be 1, or 10) instances with an optional caption field and a manual upload button per section.
The form I am uploading from is dynamically generated in layout as well as content, the uploaded files have to be stored by the handler based upon the section of the form as well as the instance of fine-uploader. I also need the ability to effectively upload each instance of fine-uploader independently
The issue that I am hitting is following the guidelines & demo for the manual upload option, ie adding a click function it will always find only the first instance as it searches for the button using .getElementById.
I can get around this by defining a new template for each instance however I would prefer to use a single template.
The template code (for each instance - abbreviated for simplicity) is
<script type="text/template" id="qq-template-manual-trigger#XX#">
<div class="qq-uploader-selector qq-uploader" qq-drop-area-text="Drop files here">
...
<div class="buttons">
<div class="qq-upload-button-selector qq-upload-button">
<div>Select files</div>
</div>
<button type="button" id="trigger-upload#XX#" class="btn btn-primary">
<i class="icon-upload icon-white"></i> Upload
</button>
</div>
...
<ul class="qq-upload-list-selector qq-upload-list" aria-live="polite" aria-relevant="additions removals">
<li>
...
<input class="caption" tabindex="1" type="text">
...
</li>
</ul>
...
</div>
</script>
<div id="fine-uploader-manual-trigger#XX#"></div>
and the uploader script
<script>
var manualUploader#XX# = new qq.FineUploader({
element: document.getElementById('fine-uploader-manual-trigger#XX#'),
template: 'qq-template-manual-trigger#XX#',
request: {
inputName: "imagegroup[]",
endpoint: '/SaveFile.aspx'
},
autoUpload: false,
debug: true,
callbacks: {
onError: function(id, name, errorReason, xhrOrXdr) {
alert(qq.format("Error on file number {} - {}. Reason: {}", id, name, errorReason));
},
onUpload: function (id) {
var fileContainer = this.getItemByFileId(id)
var captionInput = fileContainer.querySelector('.caption')
var captionText = captionInput.value
this.setParams({
"descr[]": captionText,
<-- Other parameters here -->
}, id)
}
},
});
qq(document.getElementById("trigger-upload#XX#")).attach("click", function () {
manualUploader#XX#.uploadStoredFiles();
});
</script>
in the ideal world I would prefer simply have a single
<script type="text/template" id="qq-template-manual-trigger">
....
</script>
then where required multiple times through the form
<div id="fine-uploader-manual-trigger"></div>
<script>
var manualUploader#XX# = new qq.FineUploader({
element: document.getElementById('fine-uploader-manual-trigger'),
template: 'qq-template-manual-trigger',
...
}
qq(document.getElementById("trigger-upload")).attach("click", function () {
manualUploader#XX#.uploadStoredFiles();
});
</script>
The use of the attach function by calling .getElementById just feels wrong, or at the very least cludgy, is there a better way of activating the upload on a per-instance basis?
Thanks in advance
K
Sorted, but if anyone has a better answer...
Instead of using the demo of document.getElementById("trigger-upload")
Simply use document.querySelector("#fine-uploader-manual-trigger #trigger-upload")
eg
<div id="fine-uploader-manual-triggerXX"></div>
<script>
var manualUploaderXX = new qq.FineUploader({
element: document.getElementById('fine-uploader-manual-triggerXX'),
template: 'qq-template-manual-trigger',
... // omitted for brevity
}
qq(document.querySelector("#fine-uploader-manual-triggerXX #trigger-upload")).attach("click", function () {
manualUploaderXX.uploadStoredFiles();
});
</script>

What's the best way to add the same ajax function to a list of comments?

Source code is like this:
<div>
<h4>comment content</h4>
<a id="delcmt_{{ comment.id }}">delete this comment</a>
</div>
......
<div>
<h4>comment content</h4>
<a id="delcmt_{{ comment.id }}">delete this comment</a>
</div>
I what to add ajax function to each of the "delete this comment" link:
<script type=text/javascript>
$(function() {
$('a#delcmt_id').bind('click', function() {
$.get($SCRIPT_ROOT + '/del_comment', {
}, function(data) {
$("#result").value(data.result);
});
return false;
});
});
</script>
What I can come out is using a loop to copy the upper ajax function for each comment, that must be very ugly. Any good ideas?
Try adding a class and select it with jquery add an event handler. You have to use the 'on' event because the elements you wish attach behavior to might be dynamic and load after document ready.
#*Render all this with razor, or angular or knockout*#
<div>
<h4>comment content</h4>
<span style="cursor: pointer;" id="1" data-rowid="1" class="delete-me-class">delete this comment</span>
</div>
<div>
<h4>comment content</h4>
<span style="cursor: pointer;" id="2" data-rowid="2" class="delete-me-class">delete this comment</span>
</div>
<script>
$(function () {
$('body').on('click', '.delete-me-class', function () {//http://api.jquery.com/on/ on is the latest 'live' binding for elements that may not exists when DOM is ready.
var rowId = $(this).data('rowid');
//TODO Use rowId for your delete ajax, or your element Id if you wish.
alert('You clicked on the delete link with the row ID of ' + rowId);
});
});
</script>
Here is a working Fiddle

Resources