Vue.js - Inconsistencies between model and rendered content - javascript

I have the following minimum example:
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.1/jquery.min.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<ol>
<li v-for="series in currentSeries" v-bind:data-id="series.id">
The ID is <% series.id %>
X
</li>
</ol>
</div>
</body>
<script>
vm = new Vue({
el: '#app',
delimiters: ["<%", "%>"],
data: {
currentSeries: [{id: "1"}, {id: "2"}, {id: "3"}]
},
methods: {
removeSeries: function(id) {
this.currentSeries = this.currentSeries.filter(function(element) {
return element.id != id;
});
}
}
});
$(function() {
$(document).on("click", ".removeSeries", function() {
var id = $(this).parent().data("id");
console.log(id);
vm.removeSeries(id);
});
});
</script>
</html>
I have a list of series in the variable currentSeries. I create a list of these, with adding an -tag to each item to remove it from the list.
If I click on the first 'X', the first element is removed from the list and ID 1 is shown in the console. Then, if I again click on the first element, it should remove the second element (which is now the first one). HOwever, the output id is still 1, i.e. the data-id was not updated of the node during the rendering.
What's the problem here and how to improve on this?

You are mixing jQuery with Vue and both are conceptually different. Here jQuery is entirely unnecessary because you can use Vues built in click event:
// This will call remove series on click and remove the element by the given id
X
Now, this will call your removeSeries for the given id and you can update the underlying model data however you want.
Here's the JSFiddle:
https://jsfiddle.net/vyra5nzc/
Just a note on delimiters, Vue also accepts #{{data}} as a delimiter if you are using the double mustache in some other templating engine such as laravels blade.

Related

Toggle hide/show not working on childs div

I have a script that gets data from a Google Sheet and displays it as a webpage - using JS and Tabletop.js.
There are multiple entries in the Sheet thus multiple entries in the webpage. To organise the Data I have a hide/show button. When the button is clicked on the first entry it works. However when the any of the other buttons are clicked it hides or shows the first entries data, not its own!
How do I hide/show each individual entries data? Below is the code I am working with!
I am new to JavaScript - Thanks in advance!
P.S - I struggled writing the Title to the questions!
<link href="../common/cats-copy.css" media="screen" rel="stylesheet" type="text/css" />
</head>
<style>
#add-info {
display: none
}
</style>
<body>
<div class="container">
<h1>Resturants</h1>
<div id="content"></div>
<script id="cat-template" type="text/x-handlebars-template">
<div class="entry">
<h5>{{establishment_name}}</h5>
<h6>Area: {{area}}</h6>
<h6>Cuisine: {{cuisine}}</h6>
<button id="btn" class="button-primary" onclick="myFunction()">Hide</button>
<div id="add-info">
<h6>Address: {{address}}</h6>
<h6>Google Maps: {{google_maps_location}}</h6>
<h6>Opening Times: {{opening_times}}</h6>
<h6>Rating: {{rating}}</h6>
<h6>Added By: {{added_by}}</h6>
<h6>Date Added: {{date_added}}</h6>
</div>
</div>
</script>
</div>
<!-- Don't need jQuery for Tabletop, but using it for this example -->
<script type="text/javascript" src="handlebars.js"></script>
<script type="text/javascript" src="../../src/tabletop.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
var public_spreadsheet_url = 'https://docs.google.com/spreadsheets/d/1h5zYzEcBIA5zUDc9j4BTs8AcJj-21-ykzq6238CnkWc/edit?usp=sharing';
$(document).ready( function() {
Tabletop.init( { key: public_spreadsheet_url,
callback: showInfo,
parseNumbers: true } );
});
function showInfo(data, tabletop) {
var source = $("#cat-template").html();
var template = Handlebars.compile(source);
$.each( tabletop.sheets("food").all(), function(i, food) {
var html = template(food);
$("#content").append(html);
});
}
</script>
<script>
function myFunction() {
var x = document.getElementById("add-info");
if (x.style.display === "none") {
x.style.display = "block";
} else {
x.style.display = "none";
}
}
</script>
</body>
</html>
Are all the entries on your page filled from the given template, meaning they are divs with the class entry? If so, I think your issue is the following: Your entry div has a child div with the id="add-info". And when you click the button, your handler function (myFunction()) tries to get a reference to that div via document.getElementById("add-info"); Now, if you have multiple such entries on a page, you will have multiple divs with id="add-info". But the id attribute of an element must be unique in your whole document. See the description of id or that of getElementById().
So the root cause of your problem is that the same id is used multiple times in the document when it shouldn't be. You get the behavior you're seeing because getElementById() just happens to be returning a reference to the first element it finds on the page, regardless of which button you click. But I believe you're in undefined behavior territory at that point.
One way to solve the problem is to somehow give myFunction() information about which button was clicked, while making each div you'd like to manipulate unique so they can be found easier. For instance, you can use the order of the restaurant on your page as its "index", and use that as the id of the div you'd like to hide/show. And you can also pass this index as an argument when you call your click handler:
...
<button id="btn" class="button-primary" onclick="myFunction('{{index}}')">Hide</button>
<div id="{{index}}">
<!-- The rest of the code here... -->
...
... add the index into your template context, so Handlebars can fill in the {{index}} placeholder:
...
$.each( tabletop.sheets("food").all(), function(i, food) {
food.index = i // Give your context its 'index'
var html = template(food);
$("#content").append(html);
});
...
... and then alter your function slightly to use the given argument instead of always looking for the div with id="add-info":
function myFunction(indexToToggle) {
var x = document.getElementById(indexToToggle);
// rest of the code is same
With this approach, I expect your DOM to end up with divs that have ids that are just numbers ("3", "4", etc.) and your click handler should get called with those as arguments as well.
Also note that your <button> element has id="btn". If you repeat that template on your page, you will have multiple <button>s with the same id. If you start trying to get references to your buttons via id you will have similar issues with them too since the ids won't be unique.

List created by jQuery takes a lot of time

I have a JSON array which has 9000 records and it is displayed using list li. The HTML object is created using jQuery :
$j.each(DTOList, function(index,obj) {
var $li = $j("<li/>");
var $button = $j("<button/>", { type: "button" , onClick:"location.href='playfile.html?messageId="+obj.id+"&operation=play&to="+obj.to+"&from="+obj.from+"'" });
var $parentDiv = $j("<div/>", { class: "buttonMargin" });
var $numDiv = $j("<div/>", { class: "num" ,text:obj.to});
var $nameDiv = $j("<div/>", { class: "name" ,text:obj.from});
var $timeDiv = $j("<div/>", { class: "time" , text:obj.time});
$parentDiv.append($numDiv);
$parentDiv.append($nameDiv);
$parentDiv.append($timeDiv);
$parentDiv.append($j("<hr>"));
$j("#datalist").append($li.append($button.append($parentDiv)));
});
Here is an example of li created by the above code:
<li>
<button type="button"
onclick="location.href='playfile.html?messageId=1165484222&operation=play&to=Fax Line&from=abc'">
<div class="buttonMargin">
<div class="num">Fax Line</div>
<div class="name">def</div>
<div class="time">Jan 04,2018 12:02:44 AM</div>
<hr>
</div>
</button>
<li>
The problem here is, the above code takes at least 1.5 mins to load and till then my HTML page is blank. Is there any way to improve the above code to increase performance?
Look at this performance test. Use for loop instead of .each() function.
Likely the main problem here is the DOM operation performed in this line
$j("#datalist").append($li.append($button.append($parentDiv)));
triggering document reflows over and over....
So as a first improvement try to generate all items first (without appending it into the DOM) and then append them afterwards with one single operation.
Something like this:
var elements = [];
$j.each(DTOList, function(index,obj) {
.....
elements.push($li);
});
$j("#datalist").append(elements);

How do I count the number of elements in a sortable div?

I have multiple divs in an HTML document with smaller divs within them that can be sorted among each other. When the document is loaded, a random amount of those smaller divs are appended to #boxtop.
Here's my HTML:
<html>
<body>
<head>
<title></title>
<script link src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link rel="stylesheet" href="jquery-ui.min.css">
<link rel = "stylesheet" type" type="text/css" href = "style.css">
<script src="jquery-ui.min.js"></script>
<div id = "boxtop" class = "dragInto"></div>
<button type="button" id="button">My Children!!!</button>
<div id = "eqbox"></div>
<div id = "box1" class = "ansBox"></div>
<div id = "box2" class = "ansBox"></div>
<div id = "box3" class = "ansBox"></div>
</head>
</body>
</html>
Here's my relevent jQuery
$(document).ready(function()
{
$("#boxtop").sortable
({
connectWith: ".ansBox"
});
$(".ansBox").sortable
({
connectWith: ".ansBox"
});
});
$(document).ready(function()
{
$("dragInto").droppable
({
accept: ".box"
});
});
var numChild = $("#boxtop").length;
$(document).ready(function()
{
$("#button").click(function()
{
console.log(numChild);
});
});
My question is: How can I get the number of elements in a sorted div. I currently try and print that value to the console using numChild but that prints "1". And if I were to drag a bunch of elements from #boxtop to .box1, how could I get the number of elements inside .box1?
The .length property tells you how many elements belong to the jQuery object you call it on. So given that $("#boxtop") matches one element, $("#boxtop").length will be 1.
To find out how many elements are inside #boxtop you have to select all of its descendants and then check the .length:
// All descendants:
$("#boxtop").find("*").length // or:
$("#boxtop *").length
// Or count immediate children only:
$("#boxtop").children().length
In your case I think checking immediate children is probably what you want. But don't set a global variable like:
var numChild = $("#boxtop").children().length;
...because that will set numChild to the length when the page first opens, before the user has started interacting with it. You need to check $("#boxtop").children().length or $(".box1").children().length at the point where the value is needed.
As an aside, you don't need three separate $(document).ready(...) handlers: combine all of your code into a single ready handler, or if you put your <script> element at the end of the body you don't need a ready handler at all.
You need to catch update or stop event from sortable instance. Detailed document just right there
https://api.jqueryui.com/sortable/#event-stop
https://api.jqueryui.com/sortable/#event-update
Live Demo And Working Code

How to append to appended element through JQuery?

My question is related more to why is this happening, not to knowing how to solve it.
I want to firstly append a <li> to a div and then an <a> to the <li>.
I have the following jQuery code:
$('.patches').empty();
for(i=0;i<patches.length;i++){
$('.patches').append($('<li>', {
id:'li'+ patches[i].name,
class: 'list-group-item'
}));
$('#li'+patches[i].name).append($('<a>',{
href:patches[i].downloadUrl,
text:patches[i].name,
id:'a'+patches[i].name
}));
$('#a'+patches[i].name).append('Version : '+patches[i].version+' Author : '+patches[i].author);
}
The <li> is appended but the <a> isn't, why isn't this working?
EDIT: I have modified the code as follows and it works perfectly:
$('.patches').empty();
for(i=0;i<patches.length;i++){
var listItem=$('<li>', {
id:'li'+ patches[i].name,
class: 'list-group-item'
});
$('.patches').append(listItem);
var aItem=$('<a>',{
href:patches[i].downloadUrl,
text:patches[i].name,
id:'a'+patches[i].name
});
listItem.append(aItem);
listItem.append('<p>Version : '+patches[i].version+' Author : '+patches[i].author+'</p>');
}
This version is almost the same as the previous one, I am just using variables. I would still like to know why the previous version of the code was not working. Could someone explain this?
You are querying DOM before even appending to the DOM.
Append to li directly
for(i=0;i<patches.length;i++){
$('.patches').append($('<li>', {
id:'li'+ patches[i].name,
class: 'list-group-item'
}).append($('<a>',{
href:patches[i].downloadUrl,
text:patches[i].name,
id:'a'+patches[i].name
})));
$('#a'+patches[i].name).append('Version : '+patches[i].version+' Author : '+patches[i].author);
}
You can create a link element and wrap it in a li tag, then append it to the list.
See following please:
$(document).ready(function(){
let patches = [
{name: "test1", downloadUrl: "/url1", version: 1, author: "Alessandro"},
{name: "test2", downloadUrl: "/url2", version: 2, author: "Alessandro"}];
$('.patches').empty();
for(i=0;i<patches.length;i++){
let link = $('<a>',{
href:patches[i].downloadUrl,
text: 'Version : '+patches[i].version+' Author : '+patches[i].author,
id:'a'+patches[i].name
}).wrap("<li></li>").parent();
$('.patches').append($(link, {
id:'li'+ patches[i].name,
class: 'list-group-item'
}));
}
});
$("#btnAdd").click(function(){
$("#atest2").append(";\tSome new information");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="btnAdd">Add something to second element</button>
<ol class="patches"/>
If you want to refer to a specific link element you could use its id, like:
$("#atest2").append(";\tSome new information");
I hope it helps you, bye.
These elements are dynamically created. So, you need to call something like this
$(document).on('event', '.class-name', function(){
// code
});
Modify your logic to first add <a> tag to the <li>, and then add that <li> tag to the <div>...

After using jQuery UI to sort an Ember.js item, using Ember Data's model.deleteRecord() doesn't work

I'm using jQuery UI Sortable with Ember.js to sort a list of items, and it seems to work great, until I go to delete one of the Ember Data records. The model is deleted properly, but the UI doesn't update to reflect that. If you delete the last record, an Index Out of Range error is thrown. If you delete a middle record, the one after it is removed from the DOM. If you delete the first record, it removes the first and second one from the DOM. What gives?
Given the following Handlebars:
<script type="text/x-handlebars" data-template-name="application">
<h1>Ember Data + jQueryUI Sortable Problems</h1>
{{outlet}}
Try sorting and then deleting something. The model is deleted properly, but the DOM does not reflect that. Sometimes it stays in the DOM, sometimes the wrong one is deleted, and sometimes both the right and a wrong one is deleted.
</script>
<script type="text/x-handlebars" data-template-name="index">
{{#each model}}
{{render 'formField' this}}
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="formField">
<div class="form-field" {{bind-attr data-id=id}}>
<span class="delete" {{action 'delete'}}>X</span>
{{name}} ({{displayOrder}})
<span class="handle">#</span>
</div>
</script>
And the following JavaScript:
App.IndexController = Ember.ArrayController.extend({
sortProperties: ['displayOrder'], // Force sort by displayOrder, not ID
updatePositions : function(positionData){
this.propertyWillChange('content'); // Manually notify Ember
this.get('content').forEach(function(formField) {
var key = formField.get('id');
formField.set('displayOrder', positionData[key] + 1);
});
this.propertyDidChange('content'); // Manually notify Ember
}
});
App.IndexView = Ember.View.extend({
handleSort: function(event,ui){
var positionData = {};
this.$(".form-field").each(function(index, element){
// Get model ID from bind-attr in template
var key = $(element).data('id');
positionData[key] = index;
});
this.get('controller').updatePositions(positionData);
// Delay recreating the sortable
Ember.run.next(function(){ this.makeSortable(); }.bind(this));
},
makeSortable: Ember.on('didInsertElement', function(){
try {
this.$().sortable("destroy");
} catch(err){
window.console.warn('No sortable to destroy', err);
}
finally {
this.$().sortable({
handle: '.handle',
axis: 'y',
update: this.handleSort.bind(this)
});
}
})
});
App.FormFieldController = Ember.ObjectController.extend({
actions: {
'delete': function() {
window.console.log('deleting record', this.get('name'));
this.get('model').deleteRecord();
}
}
});
Here's a fiddle.
The trick is in your use of sortProperties and {{#each model}} or {{#each content}}. The sortProperties method does not actually arrange content or model, it arranges arrangedContent. By changing it to {{#each arrangedContent}}, your problems disappear because the DOM arrangement will stay in sync with your model arrangement.

Categories

Resources