Adding and removing Meteor templates - javascript

For a Meteor application I'd like the users to be able to add divs to a column by clicking on a button, with each of these having a button to remove the div. Here's an extract of the code:
<template name="MainTemplate">
....
<a class="btn btn-sm" class="add-dynamic"></a>
<div id="add-stuff-here"></div>
....
</template>
<template name="DynamicTemplate">
<div id="dynamic-{{uniqid}}">
<a class="btn btn-sm delete-dynamic" name="{{uniqid}}"></a>
</div>
</template>
...and in the javascript file:
Template.MainTemplate.events({
'click .add-dynamic'(event) {
const random = String.fromCharCode(65 + Math.floor(Math.random() * 26));
const uniqid = random + Date.now();
Blaze.renderWithData(Template.DynamicTemplate, {uniqid: uniqid}, $("#add-stuff-here")[0])
},
})
Template.DynamicTemplate.events({
'click .delete-dynamic'(event){
const target = event.target || event.srcElement;
const id = target.id;
console.log("Button ID: " + id); // This is null
new = $('#dynamic-' + id);
new.parentNode.removeChild(new); // Therefore, this fails
}
});
Adding the templates works as expected but deleting them fails as the id of the clicked button appears to be nil.
In any case I'm probably going about this in completely the wrong way (I haven't used Meteor for a long time and didn't know it very well anyway), and so would appreciate any suggestions as to how this might be accomplished.
ETA: The possible answer suggested in the comments includes:
UI.remove(self.view);
...which "expects a template rendered with Blaze.render". I'm not able to determine how to pass the identity of the template to be removed to the function which is run on the button click - does anyone have any suggestions?

In the end I worked around it by doing this:
<template name="DynamicTemplate">
<input type="text" class="dynamic-input" placeholder="Some text" />
</template>
Template.DynamicTemplate.events({
'click .delete-dynamic'(event){
inputs = $(".dynamic-input").length;
if ( inputs > 1 ) {
const element = $(".dynamic-input")[dynamic - 1];
element.parentNode.removeChild(element);
}
}
});
That seems to do more-or-less what I was after and avoids the problem of the null ID which I mentioned in the original post.

Related

Vue 3 v-model not properly updating on in Andoid's Chrome?

I have a component, which is essentially an input/select hybrid field, which allows users to type in the input field, and select items from the dropdown, based on their query.
It works perfectly fine on most devices I've tried, i.e. as the user types something into the input field, the list of items updates and only shows those items which contain that piece of string.
Except the Chrome browser on my Android device - as you type, the list doesn't seem to update, unless I press the "space bar". Very strange. Anyone have any ideas why this might be?
Here is the code in <script setup>:
const props = defineProps([ 'items', 'searchBy' ])
const searchTerm = ref('')
const itemsToShow = computed(() => {
if (props.items) {
if (searchTerm.value) {
return props.items.filter(el => {
if (props.searchBy) {
return el[props.searchBy].toUpperCase().indexOf(searchTerm.value.toUpperCase()) > -1
}
return el.toUpperCase().indexOf(searchTerm.value.toUpperCase()) > -1
})
} else {
return props.items
}
} else {
return []
}
})
And the HTML:
<input
type="text"
class="input"
v-model="searchTerm"
placeholder=" "
/>
<div class="items-list">
<div
v-for="item in itemsToShow"
:key="item"
#click="() => handleAdd(item)"
class="item text"
>
{{ item }}
</div>
<div
v-if="!itemsToShow.length"
class="text"
>
No items match searched term
</div>
</div>
UPDATE:
I've investigated a little, and it seems the searchTerm ref, isn't updating properly, even though its bound using v-model... Still no idea why though.
I've ran into this issue before.
It seems that on certain devices, the v-model waits for a change event, instead of an input one.
Apparently, it's to do with the input method editor (IME) for the specific device.
You can check a discussion about this at https://github.com/vuejs/core/issues/5580
The workaround is to simply bind the input field with value and listen for the input event manually, e.g.
<input
type="text"
class="input"
:value="searchTerm"
#input="(e) => searchTerm = e.target.value"
placeholder=" "
/>

JavaScript stops functioning after adding type=module to src tag in HTML (Using Flask)

What I thought would be the easiest part of my project has turned into a Herculean effort. All I wanted to do was get data from a JSON file to then display on my website. Prior to using a JSON file, I hard coded some data to test my filter/search functionality, all of which I wrote in JavaScript. The code worked perfectly, so I decided to move the data to a JSON file as I am expecting to have a lot more data in the future and can't have it hardcoded. However, I have been unable to get data from the JSON file successfully. I tried using require('./data.json'), but apparently I can't just use require like that. I then tried importing the file, which only works if I go back to the html and add type="module" to the src tag. This then allows all of the data to display on the webpage, however, the function that allows me to filter by category no longer works. When I click on the buttons, I get no response. I used Inspect to get the console to find the error, and the output is:
Uncaught ReferenceError: filterProject is not defined
The search functionality still works, and I suspect this is because that code isn't inside a function. Thus, I don't know why filterProject is supposedly not defined when the other JS code works. Here is all of my code:
import projects from './data.json' assert { type: "json" };
const path = "http://localhost/static/images/";
//ADDING THE HTML, IGNORE
for(let i of projects){
let card = document.createElement("div");
card.classList.add("card", i["category"], "hide");
let imgContainer = document.createElement("div");
imgContainer.classList.add("image-container");
let imageOne = document.createElement("img");
imageOne.setAttribute("src", path.concat(i["imageOne"]));
imgContainer.appendChild(imageOne);
card.appendChild(imgContainer);
let container = document.createElement("div");
container.classList.add("container");
let name = document.createElement("h3");
name.classList.add("project-name");
name.innerText = i["projectName"].toUpperCase();
container.appendChild(name);
let student = document.createElement("h4");
student.classList.add("student-name");
student.innerText = i["studentName"].toUpperCase() + " mentored by " + i["mentor"].toUpperCase();
container.appendChild(student);
let category = document.createElement("h6");
category.innerText = i["category"].toUpperCase().replace("_", " ");
container.appendChild(category);
card.appendChild(container);
document.getElementById("projects").appendChild(card);
}
//FILTERING (DOESNT WORK)
function filterProject(value){
let buttons = document.querySelectorAll(".button-value");
buttons.forEach((button) => {
if(value.toUpperCase() == button.innerText.toUpperCase()){
button.classList.add("active");
}else{
button.classList.remove("active");
}
});
let elements = document.querySelectorAll(".card");
elements.forEach((element) => {
if(value == "all"){
element.classList.remove("hide");
}
else{
//having a space messes it up, make it _
if(element.classList.contains(value.replace(" ", "_"))){
element.classList.remove("hide");
}
else{
element.classList.add("hide");
}
}
});
}
//SEARCH (WORKS)
document.getElementById("search").addEventListener
("click", () => {
let searchInput = document.getElementById("search-input").value;
let elements = document.querySelectorAll(".student-name");
let cards = document.querySelectorAll(".card");
elements.forEach((element, index) =>{
if(element.innerText.includes(searchInput.toUpperCase())){
cards[index].classList.remove("hide");
}
else{
cards[index].classList.add("hide");
}
});
});
//INTIAL STATE
window.onload = () =>{
filterProject("all");
};
Here is the HTML just in case as well:
<div class ="wrapper">
<div id="search-container">
<input type="search" id="search-input" placeholder="Search student name here..."/>
<button id = "search">Search</button>
</div>
<div id ="buttons">
<button class = "button-value" onclick="filterProject('all')">All</button>
<button class = "button-value" onclick="filterProject('Creative Project')">Creative Project</button>
<button class = "button-value" onclick="filterProject('Developing Voice')">Developing Voice</button>
<button class = "button-value" onclick="filterProject('Interdisciplinary Fusion')">Interdisciplinary Fusion</button>
<button class = "button-value" onclick="filterProject('Personal Writing')">Personal Writing</button>
<button class = "button-value" onclick="filterProject('Curriculum Designer')">Curriculum Designer</button>
<button class = "button-value" onclick="filterProject('Internship')">Internship</button>
</div>
<div id = projects></div>
</div>
<script type = "module" src = "{{ url_for('static',filename='javascript/script.js') }}"></script>
If it matters, I am using Flask as my web framework. I'm not sure if that has any impact on anything, but it has created some obstacles when I've tried to create a live server to solve this issue. Thanks in advance for any replies!
What you're looking for is how to load json files locally.
One solution is
Start a local server e.g. http://localhost:8080
Then use fetch() to retrieve the json file
For example, if your data.json file was located within the same folder where you have your html file and where you started your server, then your code could be something like
fetch("http://localhost:8080/data.json")
.then((response) => {
return response.json();
})
.then((data) => {
// Add code to process your data
})

Vue - Content of contenteditable is doubling without variable change

I have 4 contenteditables which I am copying to a variable.
If I write "1234" in Path2, then change to Title1 and then defocus Title1, then Path2 will change to "12341234", but the variable this.PC.Path2 will still be "1234".
This error occurs on only content editable - Path2.
I have changed the name so only the Function updateHtml can interact with it(on my site) - no effect.
<div class="PathContainer" v-on:click="focusPath2">
<div class="namespace edit path" contenteditable="true" #blur="updateHtml" name="Path">{{PC.Path}}</div>
<div class="path editPath2" contenteditable="true" #blur="updateHtml" name="Path2" ref="Path2" >{{PC.Path2}}</div>
</div>
Title1: <span class="edit" contenteditable="true" #blur="updateHtml" name="Title2">{{ PC.Title2 }}</span><br>
Title2: <span class="edit" contenteditable="true" #blur="updateHtml" name="Title1">{{ PC.Title1 }}</span>
updateHtml: function(e) {
var foo = e.target.getAttribute("name")
console.log("PATH2---", this.PC.Path2);
this.PC[foo] = e.target.innerText;
console.log("PATH2---", this.PC.Path2);
console.log("UPDATING this.PC." + foo, " to-->", this.PC[foo]);
},
The Complete Code with Backend is on: https://github.com/i5heu/wikinota/tree/V2
Only the Code of this Module: https://github.com/i5heu/wikinota-web/blob/04629560ec0d6383b4ad8f92cb2647a616c559ce/js/pedit.js
Here is the Chrome Performance Profile. (with Pictures of the weird stuff)
weird-bug.zip
I'm using Vue.js v2.5.16.
I have found a workaround for this problem.
I think the problem is in the DOM engine of Vue.
So you have to trigger the DOM engine again to overwrite the content of the content-editable.
This is achieved by 1 Line.
updateHtml: function(e) {
var foo = e.target.getAttribute("name");
console.log("PATH2---", this.PC.Path2);
this.PC[foo] = e.target.innerText;
e.target.innerText = this.PC[foo]; //<<<< This is the workaround
console.log("PATH2---", this.PC.Path2);
console.log("UPDATING this.PC." + foo, " to-->", this.PC[foo]);
}

Adding/removing input fields

I'm pretty new to ReactJS, I'm liking it a lot, but there are some things like binding that seems to be easier in Angular.
I want to have a form, where a user can click a button to add extra input fields. At any point, they can also "delete" an input field.
On the submit, I want to get these inputs as an array, i.e. pass dynamicInputs to my API which contains an array of name.
This is what I've done (which is probably wrong since I'm treating React like Angular):
var React = require('react');
module.exports = React.createClass({
addInputField: function(e) {
e.preventDefault();
var inputs = this.state.inputs;
inputs.push({name: null});
this.setState({inputs : inputs});
},
removeInputField: function(index) {
var inputs = this.state.inputs;
inputs.splice(index, 1);
this.setState({inputs : inputs});
},
handleSubmit: function (e) {
e.preventDefault();
// What do I do here?
},
getInitialState: function() {
return {inputs : []};
},
render: function (){
var inputs = this.state.inputs;
return (
// Setting up the form
// Blah blah
<div className="form-group">
<label className="col-sm-3 control-label">Dynamic Inputs</label>
<div className="col-sm-4">
{inputs.map(function (input, index) {
var ref = "input_" + index;
return (
<div className="input-group">
<input type="text" className="form-control margin-bottom-12px" placeholder="Enter guid" value={input.name} ref={ref} aria-describedby={ref} />
<span className="input-group-addon" onClick={this.removeInputField.bind(this, index)} id={ref} ><i className="fa fa-times"></i></span>
</div>
)
}.bind(this))}
<button className="btn btn-success btn-block" onClick={this.addInputField}>Add Input</button>
</div>
</div>
);
}
});
Right now removeInputField does NOT work! It just removes the last entry all the time.
Every <div className="input-group"> must have a unique key
<div className="input-group" key={index}>
That's how React distinguishes between collection of rendered nodes.
References:
https://facebook.github.io/react/docs/multiple-components.html#dynamic-children
UPD:
As #WiredPrairie mentioned below in the comments - the suggested solution is far from ideal, since the index is not unique enough. And instead you need to create another array with some unique identifiers (a monotonously growing sequence would be enough) and maintain it in parallel with this.state.inputs and use its values as keys.
So, on adding an element you:
this.keys.push(++this.counter);
on removing - remove from both by the same index. And in the .map you
<div className="input-group" key={this.keys[index]}>

Double click to edit element in Meteor app

I am making a school project in Meteor.js for a hospital - the prototype of the app is up on http://lrh.meteor.com . In the view doctors section in the table, I want to double click on the record and be able to edit the Name and the email id, but along with this I also want to update in record in MongoDB collection. Any ideas about how I can implement this feature?
I think this can help you.
Lets create this helper.
Template.example.helpers({
'editValue' : function(){
return Session.get("TargetValue" + this._id);
}
})
And this 2 events.
Template.example.events({
'dbclick #spanIdOnDom' : function(e,t){
return Session.set("TargetValue" + t.data._id,true)//hide the span and we set the input
},
'click #buttonToSaveNewValue': function(e, t) {
//here you can take the emailId and the name based on this._id like this Collection.find({_id:this._id}).fetch(); and do the updates you want to do
var newValueFromInput = document.getElementById('newValueFromInput').value;
var idCurrentDocument = this._id;
var Bebida = Collection.findOne(t.data._id);
Collection.update({_id: idCurrentDocument}, {$set:{fieldToUpdate: newValueFromInput}});
return Session.set("TargetValue" + t.data._id,false); //we hide the input and we put the span again
}
})
HTML
<template name="example">
{{#each collectionFind}}
{{#if editValue}}
<input type="text" id="newValueFromInput" value="{{currentValue}} " />
<button class="btn btn-sm btn-primary" id="buttonToSaveNewValue" type="submit">Save new Value</button>
{{else}}
<td>
<p>
<span class="content col-md-10" id="spanIdOnDom" ><h4>Descripcion Bebida:</h4><br>{{currentValue}} </span>
</p>
</td>
{{/if}}
{{/each}}
</template>
Of course you need to set your Allow/deny Permission and publish/subscribe methods to make it work more efficient.
How it works?
in resume you have an <span> tag with the current value, when you dobleClick on the <span> tag , we set the Session to true, the <span> tag go away and a new <input> appears with a new button then we take the value from the <input> and she update ($set) into the collection, and its done.
NOTE: this is a mini-repo from Simple Crud app in Meteor from Raj Anand, but the code on the blogs is on coffee and i don't use coffee Script.

Categories

Resources