jQuery/JavaScript preventing duplicate appends - javascript

seemingly minor issue I'm having with a mock-forum system I'm trying to prototype, as the following array and table function I've created duplicates a forum table the screen due to the appends I've used, and I'm not sure how to go about avoiding this or amending this (I.e, if the user repetitively clicks the forum page, the forum will repetitively generate). I'm still fairly knew to the language, but from what I've found from similar posts, everything I tried to apply hasn't resolved the issue.
var index;
var topics = [
{title: "Football Topics", followers: 280, articles: 5, posts: [
{postId: 101, contents: "Dummy content", author: "Dummy content", replies:[
{replyId: 1, contents: "Dummy content", author: "Dummy content"},
{replyId: 2, contents: "Dummy content", author: "Dummy content"},
{replyId: 3, contents: "Dummy content", author: "Dummy content"}
]},
]}
];
var topicTable = $("<table class='forumTable'><tr><th>Title</th><th>Posts</th><th>Followers</th></tr></table>");
//console test
//console.log(topics);
//Looping all topics in variable "topics"
for (index in topics) {
//console test
console.log(topics[index].title);
var row = $("<tr></tr>");
row.append("<td>" + topics[index].title + "</td>");
row.append("<td>" + topics[index].articles + "</td>");
row.append("<td>" + topics[index].followers + "</td>");
topicOnClick(row, topics[index]);
topicTable.append(row);
}
page.append(topicTable);

Based on what you've given I think there are a couple of options.
Option 1
You say if the use repetitively 'clicks' the forum page implying there's a click handler. You can use .one which executes the function at most on times.
var table = "<table><tr><td>column1</td><td>column2</td></tr></table>";
$('button').one('click', function() {
$('#tableContainer').append(table);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div id="tableContainer">
</div>
<button>add</button>
</body>
Option 2
You could target the container div and use .html() this will allow the function to run on each click but will write over the content so it gives the appearance of only being loaded once.
var table = "<table><tr><td>column 1</td><td>column 2</td></tr></table>";
$('button').on('click', function(){
$('#tableContainer').html(table);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div id="tableContainer">
</div>
<button>add</button>
</body>

Related

How to fully create HTML from a js object?

I am working on finding a way to use javascript to create blog posts for my website. I am quite knowledgeable in javascript, however I haven't used many DOM elements other than document.getElementById(). I am looking for support in how to implement a way to turn a JS object into a post. Here is an example object:
var posts = { //My object
firstPost: { //An example post
index: 1, //Identifies order of posts: 1 is oldest... >1 newest
id: "first", //An id for the post
date: { //Date will be listed next to name on post
month: 11,
day: 2,
year: 2018
},
name: "My Post", //Name of the post
text: "Text for the post...", //Actual Post
image: 'blogImage.png' //An image for the post
}
And now I want to pass it through a function as shown below to create HTML:
function assemblePost( index, id, month, day, year, text, image) {
//DOM GOES IN HERE
}
Here is an example of how the HTML looks when I type it manually:
<div class="card" id="first"> <!-- Card element links to css -->
<h2>My Post</h2> <!-- Name of the post -->
<h5>Posted November 2nd, 2018</h5> <!-- Date of the post -->
<div class="img" style="height:200px;"><img src="/blogImage.png" ></div>
<p>Text for the post..."</p> <!-- Actual Post -->
</div>
I am not entirely sure how to approach this because:
The class, "card", links to the CSS, and I am not sure how to implement that with DOM.
I am not sure if DOM can edit style, as in this example, the image height is 200, but in another image it could be different.
For many of my other posts, I may have multiple paragraphs and/or lists. At the very least, I wanted a way to create one string of text. Maybe for lists, an array would be most useful in my object, however I am unsure how to address multiple paragraphs.
I realize it is a lot of explaining, examples, and guidelines, but I really do appreciate your help! Thank you!
Just do it step by step.
var posts = [ //My object (array of posts)
{ //An example post
index: 1, //Identifies order of posts: 1 is oldest... >1 newest
id: "first", //An id for the post
date: { //Date will be listed next to name on post
month: 11,
day: 2,
year: 2018
},
name: "My Post", //Name of the post
text: "Text for the post...", //Actual Post
image: 'blogImage.png' //An image for the post
},
{ //An example post
index: 2, //Identifies order of posts: 1 is oldest... >1 newest
id: "first", //An id for the post
date: { //Date will be listed next to name on post
month: 11,
day: 2,
year: 2018
},
name: "My another Post", //Name of the post
text: "Text for another post...", //Actual Post
image: 'blogImage.png' //An image for the post
}
];
const container = document.getElementById('div-posts');
posts.forEach(function(post) {
let div = document.createElement('div');
div.className = 'card';
div.id = 'post_' + post.index; //or post.id provided itis unique
//create more elements instead of ".innerHTML" if you wish
div.innerHTML = '<h2>' + post.name + '</h2>' +
'<h5>' + (new Date(post.date.year, post.date.month, post.date.day).toDateString()) + '</h5>' +
'<div class="img"><img src="/' + post.image + '" ></div>' +
'<p>' + post.text + '</p>';
container.appendChild(div);
});
.card {
border: solid 1px #ccc;
width: 400px
}
.img img {
max-width: 100%
}
<div id="div-posts"></div>

AngularJS, ng-repeat doesn't repeat

I'm new in AngularJs and I'm facing a problem with ng-repeat. Basically, I want to get the comments relative to an article from my database with ajax, then to display them with ng-repeat. I then have an array in which I push my comments. My problem is that when I manually create a comment in my array, it works well, but if I push automatically this comment from the callback of my ajax function, the array is updated, but not my view.
View in html :
var articleApp = angular.module('articleApp', []);
articleApp.controller('CommentsController', function CommentsController($scope) {
$scope.comments = [];
// This push works well, my view is updated
$scope.comments.push({
content: "Hello world !",
date: "2 minutes ago",
id: 29,
author: {
pseudo: "Sean"
}
});
// When I push with this function, my array is updated, but not the view
$scope.addComment = function(comment) {
$scope.comments.push({
content: comment.comment,
id: comment.id,
date: comment.date_post,
author: {
id: comment.author.id,
pseudo: comment.author.pseudo
}
});
};
var articleID = document.getElementById('articleID').textContent;
// getComments is defined elsewhere, and returns the 20 first comments
getComments(20, articleID, 0, function(comments) {
for(var i = 0; i < comments.length; i++) {
$scope.addComment(comments[i]);
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<section id="commentsSection" class="bottom_apps" ng-controller="CommentsController as comments">
<article id = "{{comment.id}}" class="comment_container" ng-repeat="comment in comments">
<div class="comment_header">
<span class="comment_author">{{comment.author.pseudo}}</span>
<span class="comment_date">{{comment.date}}</span>
</div>
<div class="comment_content">
{{comment.content}}
</div>
</article>
</section>
I double-checked, triple-checked all my code, but I can't see where I did a mistake.
Looks like your getComments works asynchronously, since you are passing a callback function which has the comments as a parameter.
Therefore, even though you update your comments inside that callback, AngularJS does not seem to "notice it", right?
This is because you have to tell AngularJS explicitly to run a new digest cycle.
In short, just add $scope.$apply() to the end of your callback:
getComments(20, articleID, 0, function(comments) {
for(var i = 0; i < comments.length; i++) {
$scope.addComment(comments[i]);
}
$scope.$apply();
});
To learn more about this, search for "AngularJS digest cycle". In short, the thing is: AngularJS updates everything in what are called digest cycles. If a digest cycle does not happen, AngularJS will not "notice" the changes. When things runs synchronously, AngularJS automatically runs digest cycles. But for many asynchronous things, AngularJS can't figure it out automatically, so you have to tell AngularJS explicitly to perform a digest cycle.
You can try doing something like below code, Also please check this plunker link for your given example scenario with some dummy action.
Controller:
$scope.comments = [];
$scope.comment={};
// This push works well, my view is updated
$scope.comments.push({
content: "Hello world !",
date: "2 minutes ago",
id: 29,
author: {
pseudo: "Sean"
}
});
$scope.comments.push({
content: "Hello world 2!",
date: "5 minutes ago",
id: 30,
author: {
pseudo: "Jack"
}
});
// When I push with this function, my array is updated, but not the view
$scope.addComment = function() {
$scope.comments.push({
content: $scope.comment.comment,
id: $scope.comments.length+1,
date: new Date(),
author: {
id: $scope.comments.length+1,
pseudo: $scope.comment.comment
}
});
console.log($scope.comments);
};
Template:
<section id="commentsSection" class="bottom_apps" ng-controller="CommentsController">
<input type="text" ng-model="comment.comment"/>
<button type="button" ng-click="addComment()">Add Comment</button><br/>
<article id = "{{comment.id}}" class="comment_container" ng-repeat="comment in comments">
<div class="comment_header">
<span class="comment_author">{{comment.author.pseudo}}</span>
<span class="comment_date">{{comment.date}}</span>
</div>
<div class="comment_content">
{{comment.content}}
</div>
</article>
</section>

Data Insertion From A Text File Into A Complex Table Layout (HTML, PHP, JS)

I'm building a webpage that utilizes a fairly complex table layout which will hold a decent amount of data. I don't want to hardcode all this data in, so I would like to read it in from a text file and dynamically create the webpage that way. The following is an example I have come up with to illustrate what I am trying to accomplish.
// imagine this as the basket that holds all the groceries
<table id=”basket”>
// this is the label for a new section of the basket (like fruits, or vegetables)
<thead>Fruits</thead>
// this is the section of the basket for the items described by the label above
<tbody>
<tr>
<td>
// this is the container of these items
<table class="category">
// a new section of the container for each item
<tr>
<td>
// information about this particular item
<table class="Item">
<tr><td>Strawberry</td></tr>
<tr><td>Red</td></tr>
<tr><td>Seeds</td></tr>
</table>
</td>
// verbose description about this item
<td>Strawberries are sweet and grow in the summer</td>
</tr>
So if I had data like the following:
Fruits
strawberry, red, seeds, Strawberries are sweet and grow in the summer
blueberry, blue, seeds, Willy Wonka likes these
avocado, green, pit, Still not sure how I feel about these
Vegetables
Celery, green, stalk, A top tier vegetable
Radish, Red, bulb, I still haven't warmed up to these
Potato, Brown, root, A classic!
Underneath the basket table I would have two instances of this code, one for fruits and one for vegetables
// this is the label for a new section of the basket (like fruits, or vegetables)
<thead>Fruits</thead>
// this is the section of the basket for the items described by the label above
<tbody>
<tr>
<td>
// this is the container of these items
<table class="category">
and within each of those sections I would have however many instances of this code as called for, (in this case, 3 for each because there are 3 fruits and 3 vegetables listed)
// a new section of the container for each item
<tr>
<td>
// information about this particular item
<table class="Item">
<tr><td>Strawberry</td></tr>
<tr><td>Red</td></tr>
<tr><td>Seeds</td></tr>
</table>
</td>
// verbose description about this item
<td>Strawberries are sweet and grow in the summer</td>
</tr>
So my ultimate question is,
What is the best way for me to structure a text file in order to accomplish this
and
With what kind of PHP or JavaScript could I successfully read this properly formatted text file, and then generate the HTML I want that contains the correct data
Any advice or insight would be appreciated, thank you.
Okay personally I'd recommend saving your data in json format which is a pretty standard way to represent this type of data. You could then supply it via AJAX or save it in a file and include it with a script tag. Really all depends on your requirements.
Also I don't know what your UI requirements are but I'd personally not create nested tables like this. I would use div tags and write custom css to get the layout I wanted.
// This could be supplied via AJAX or you could save it in a javascript or json file.
// It really depends on your requirements.
var jsonData = [
{
name: "Fruits",
items: [
{
name: "Strawberry",
color: "red",
type: "seeds",
description: "Strawberries are sweet and grow in the summer"
},
{
name: "Blueberry",
color: "blue",
type: "seeds",
description: "Willy Wonka likes these"
},
{
name: "Avocado",
color: "green",
type: "pit",
description: "Still not sure how I feel about these"
}
]
},
{
name: "Vegetables",
items: [
{
name: "Celery",
color: "green",
type: "stalk",
description: "A top tier vegetable"
},
{
name: "Radish",
color: "red",
type: "bulb",
description: "I still haven't warmed up to these"
},
{
name: "Potato",
color: "brown",
type: "root",
description: "A classic!"
}
]
}
]
// This is the javascript code that would read your json data and build HTML from it
document.getElementById("basket").innerHTML = getBasketHTML(jsonData)
function getBasketHTML(data) {
// loop through data
var HTML = ""
for (var i = 0, i_end = data.length; i < i_end; i++) {
var category = data[i]
// add new row and table for each category
HTML += "<tr><table>"
// add category label
HTML += "<thead>" + category.name + "</thead>"
// add category table
HTML += "<tbody><tr><td><table class='category'>"
// loop through category items and build HTML
for (var j = 0, j_end = category.items.length; j < j_end; j++) {
HTML += getItemHTML(category.items[j])
}
// close category table
HTML += "</table></td></tr></tbody></table></tr>"
}
return HTML
}
function getItemHTML(item) {
// opening row tag
var HTML = "<tr>"
// add item information
HTML += "<td><table class='Item'>"
HTML += "<tr><td>" + item.name + "</td></tr>"
HTML += "<tr><td>" + item.color + "</td></tr>"
HTML += "<tr><td>" + item.type + "</td></tr>"
HTML += "</table></td>"
// add verbose description
HTML += "<td>" + item.description + "</td>"
// closing row tag
HTML += "</tr>"
return HTML
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<table id="basket"></table>
</body>
</html>

Javascript: If then statement not working in function

I'm building a personality-type quiz and having difficulty tailoring the results page as I'd like it to. The if statement, contained in the display_scenario function, isn't working. I want it to generate a different final message depending on the value of the countValue variable but it's not working. If you are able to help me understand why it's not working, I'd appreciate it.
The key pieces of code are below. It's also in place on Codepen: http://codepen.io/msummers40/pen/eZaePe
Essentially, the quiz:
- asks people a series of questions
- each answer has a numeric weight assigned to it
- this numeric weight is tallied in the countValue variable
- I want to use the countValue variable as a way to generate one of four final pages
I've tried to set up an if statement at the end of the quiz but it's just not working and I'm unsure of how to get this to work - it seems to be building the final view regardless of what's in the if statement.
If you are able to help, thank you.
<body>
<!-- QUIZ BEGINS -->
<div id="wrapper">
<h2 id="cyoaHeader">TITLE HERE<br><small>SUBHED</small></h2>
<div id="quizImage"> </div>
<div id="prompt"> </div>
<div id="options"> </div>
<div id="buildingStatus"> </div>
</div>
<div id="addLinks"> </div>
<!-- QUIZ ENDS -->
<script>
// JSON for quiz
// Contains the questions, images and variable to give answers weight/direction
var story = {
intro: {
prompt: 'You need to get up and out and get on with a big task - what song would propel you out of bed?',
quizImage: '<img id="quizImage" src="https://upload.wikimedia.org/wikipedia/commons/a/ab/Semi-nude_woman_getting_out_of_bed_(rbm-QP301M8-1887-265a~5).jpg"><br />',
options: [{
name: 'Song 4',
path: 'questionTwo',
addition: 4
}, {
name: 'Song 3',
path: 'questionTwo',
addition: 2
}, {
name: 'Song 2',
path: 'questionTwo',
addition: 3
}, {
name: 'Song 1',
path: 'questionTwo',
addition: 1
}]
},
questionTwo: {
prompt: 'Sentence.<br/><br/>Question 2?',
quizImage: '<img id="quizImage" src="http://www.allgifs.com/wp-content/uploads/2013/09/pancake.gif">',
options: [{
name: 'A',
path: 'result',
addition: 4
}, {
name: 'B',
path: 'result',
addition: 3
}, {
name: 'C',
path: 'result',
addition: 2
}, {
name: 'D',
path: 'result',
addition: 1
}]
},
result: {
prompt: 'This is the default statement on the results page. I need to make it tailored to the countValue variable',
quizImage: '<img id="quizImage" src="http://viralgifs.com/wp-content/uploads/2014/03/cat_did_u_forget.gif">',
options: [{
name: 'FINAL CTA',
path: 'intro',
addition: 0
}]
}
};
//establishing counter for weighted answer
var countValue = 0;
console.log(countValue);
var additionInt = 0;
console.log(additionInt);
// Option is an object with properties {name and path objects, addition array}
function display_scenario(options) { //value passed had been chosen_options
var option_name = options.name;
var option_path = options.path;
var additionInt = options.addition;
countValue += additionInt;
// countValue = (additionInt + countValue);
console.log(additionInt);
console.log(countValue);
var scenario = story[option_path];
// Clear the #prompt, #quizImage, #options, #addLinks, #buildingStatus divs before writing new ones in if statment below -- GROUP THESE IN A FUNCTION BEFORE GO LIVE
jQuery('#prompt').empty();
jQuery('#quizImage').empty();
jQuery('#options').empty();
jQuery('#addLinks').empty();
jQuery('#buildingStatus').empty();
// THIS IF ELSE STRUCTURE DOESN'T SEEM TO BE WORKING - I'M TRYING TO SET IT UP SO THAT IT DISPLAYS ONE OF FOUR OPTIONS, DEPENDING ON THE VALUE OF THE countValue VARIABLE. ATM ONLY THREE OPTIONS ARE IN PLACE; NONE ARE WORKING.
// Create a <p> to display what the user has chosen if option_name is not null and append it to the #prompt <div>
if (options.name != 'result') {
jQuery('<p>').html('<i>You selected <b>' + option_name + '</b></i><br><p>And the running total is ' + countValue + '</p>').appendTo('#buildingStatus');
// Append the scenario's prompt
jQuery('<p>').html(scenario.prompt).appendTo('#prompt');
// Appending an image
jQuery('<p>').html(scenario.quizImage).appendTo('#quizImage');
// Appending links in the addLinks div
jQuery('<div>').html(scenario.addLinks).appendTo('#addLinks');
} else if (options.name == 'result' && countValue<17) {
jQuery('<p>').html('<i>Yay! This option worked. You selected <b>' + option_name + '</b></i><br><p>And the running total is ' + countValue + '</p>').appendTo('#buildingStatus');
// Append the scenario's prompt
jQuery('<p>').html(scenario.prompt).appendTo('#prompt');
// Appending an image
jQuery('<p>').html(scenario.quizImage).appendTo('#quizImage');
// Appending links in the addLinks div
jQuery('<div>').html(scenario.addLinks).appendTo('#addLinks');
} else {
jQuery('<p>').html('<i>Yay! This option worked. You selected <b>' + option_name + '</b></i><br><p>And the running total is ' + countValue + '</p>').appendTo('#buildingStatus');
// Append the scenario's prompt
jQuery('<p>').html(scenario.prompt).appendTo('#prompt');
// Appending an image
jQuery('<p>').html(scenario.quizImage).appendTo('#quizImage');
// Appending links in the addLinks div
jQuery('<div>').html(scenario.addLinks).appendTo('#addLinks');
};
// Append the options into the #options <div>
// We want to loop through all the options and create buttons for each one. A regular for-loop would not suffice because adding a button is not asynchronous. We will create an asynchronous loop by using recursion
function add_option_button(index) {
if (index === scenario.options.length) {
// Base case
return;
}
var option = scenario.options[index];
// Create a <button> for this option and append it to the #options <div>
jQuery('<button>')
.html(option.name)
.click(function(e) {
// This is an onclick handler function. It decides what to do after the user has clicked on the button.
// First, prevent any default thing that the button is going to do, since we're specifying our own action for the button
e.preventDefault();
// We'll want to call display_scenario() with this option
display_scenario(option);
})
.appendTo('#options');
// Add the next option button
add_option_button(index + 1);
}
add_option_button(0);
}
// This function waits until the document is ready
jQuery(document).ready(function() {
// Start the quiz
display_scenario({
name: null,
path: 'intro',
addition: 0
});
});
</script>
</body>
</html>
I think you need to recalculate the sum whenever the user changes something, apart from displaying the scenarios everytime. Split the display_scenario function into one to display the options, and into separate logic to sum up all options choosen.

JQuery Cascade plugin to remember last selected state

I'm using the JQuery cascade plugin (here)
I have it so I can select a book section in the first dropdown, which populates with chapters in the second drop down. e.g:
var list1 = [
{'When':'0','Value':'0','Text':'Chapter 1'},
{'When':'0','Value':'1','Text':'Chapter 2'},
{'When':'0','Value':'2','Text':'Chapter 3'},
...(snip..)
{'When':'1','Value':'0','Text':'Appendix 1'},
{'When':'1','Value':'1','Text':'Appendix 2'},
{'When':'1','Value':'2','Text':'Appendix 3'},
....(snip..)
];
function commonTemplate(item){
return "<option value='" + item.Value + "'>" + item.Text + "</option>";
};
function commonTemplate2(item) {
return "<option value='" + item.Value + "'>***" + item.Text + "***</option>";
};
function commonMatch(selectedValue) {
return this.When == selectedValue;
};
Which is then called by:
jQuery(document).ready(function()
{
jQuery("#section").cascade("#chapter",{
list: list1,
template: commonTemplate,
match: commonMatch
});
});
my form is pre-populated with section0 and it's associated chapters. Everything works fine - when I select book 2, the script fires and populates the second drop down with the chapters from book 2 etc.
My problem is: When I select a book like "Section 2, Chapter 3", I would like that book and chapter to load while the form on the page displays "Section 2, Chapter 3". This way the user can quickly go to chapter 4 or whatever. Currently, when Section 2, Chapter 3 loads, the form defaults to "Section 1, Chapter 1" which is the default form value from the first time the user arrived at the page.
My Question is: How can I have a default value the first time, but when a user submits the form, the page reloads remembering the last selected section/chapter in the form?
Thanks.

Categories