My task is to take the 3 different color lists in the jsonObj and place them into a <ul>. They should only appear one at a time, every second. For the sake of the fiddle, I put it to every 5 seconds.
I haven't gotten to the 2nd or 3rd list of colors yet because while I can list out my 1st color list, they're appending outside of the listItem I've created for them. The code it spits it is:
var jsonObj = '{"one":["red","green","blue"], "two":["red","green","blue"], "three":["orange","purple","hotpink"]}',
object = JSON.parse(jsonObj),
cOne = object.one,
cTwo = object.two,
cThree = object.three,
i = 0,
timer;
$('body').append('<ul/>');
timer = setInterval(function() {
$.each(cOne, function() {
var list = $('body ul'),
listItem = $(list).append('<li>'),
html = $(listItem).append(cOne[i]);
if (i < cOne.length) {
i++;
$(cOne[i]).split("");
list.append(html);
} else if (i = cOne.length) {
i = 0;
}
});
}, 5 * 1000);
timer;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Also available at https://jsfiddle.net/ep76ba3u/
What it does:
<ul>
<li></li>
"red"
<li></li>
"blue"
</ul>
What it should look like:
<ul>
<li>red</li>
<li>blue</li>
</ul>
I've tried rearranging it all. I've tried using wrap, innerWrap. I've tried just using text() and a few other methods. I started working on it at 3am and its 5am now... brain is fried. Any idea how to get this moving is appreciated.
You can not append partial html, that's why this $(list).append('<li>') is immediately closing the <li>.
And you should not modify the markup in a loop. It's obnoxious and unperformant.
Check out this approach to your code:
var jsonObj = '{"one":["red","green","blue"], "two":["red","green","blue"], "three":["orange","purple","hotpink"]}',
object = JSON.parse(jsonObj),
iteration = 0,
timer;
$('body').append('<div id=container>');
//a few utilities, because I don't want to repeat myself all over the place:
var string = value => value == null ? "" : String(value);
var wrapInNode = nodeName => value => `<${nodeName}>${ string(value) }</${nodeName}>`;
//here I create a few utility-methods that will build my markup:
var li = wrapInNode('li');
var ul = wrapInNode('ul');
var header = wrapInNode('h4');
timer = setInterval(function() {
//building the complete markup and adding it at once
var blocks = [],
//how many rows should I show in this iteration
numRowsLeft = ++iteration,
//getting this result is just a nice sideeffect of using `every()` instead of `forEach()`
//to short-curcuit the loop
done = Object.keys(object)
.every(function(key) {
//this line makes the title to be added with as a distinct iteration and not with the first item,
//check out what happens when you remove it
--numRowsLeft;
var rows = object[key]
//shorten the Array to numRowsLeft, if necessary
.slice(0, numRowsLeft)
//wrap each item in a li-node with my predefined utility-function
.map(li);
numRowsLeft -= rows.length;
//building the markup for this block
blocks.push(header(key) + ul(rows.join("")));
//here I'm short circuiting the loop. to stop processing the other keys on Object
return numRowsLeft > 0;
});
$('#container').html(blocks.join(""));
if (done) {
clearInterval(timer);
}
}, 1000);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
And showing the header all the time while only adding the points:
var jsonObj = '{"one":["red","green","blue"], "two":["red","green","blue"], "three":["orange","purple","hotpink"]}',
object = JSON.parse(jsonObj),
iteration = 0,
timer;
$('body').append('<div id=container>');
var string = value => value == null ? "" : String(value);
var wrapInNode = nodeName => value => `<${nodeName}>${ string(value) }</${nodeName}>`;
var li = wrapInNode('li');
var ul = wrapInNode('ul');
var header = wrapInNode('h4');
timer = setInterval(function() {
var numRowsLeft = ++iteration,
blocks = Object.keys(object)
.map(function(key) {
var rows = object[key]
.slice(0, numRowsLeft)
.map(li);
numRowsLeft -= rows.length;
return markup = header(key) + ul(rows.join(""));
});
$('#container').html(blocks.join(""));
// If I'd had room to show even more rows, then I' done
if (numRowsLeft > 0) {
clearInterval(timer);
}
}, 1000);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
I feel compelled to put in an answer which should perform better by cache of the jQuery objects and processes the objects and each color in them, hitting DOM once for each color.
var jsonObj = '{"one":["red","green","blue"], "two":["red","cyan","darkblue"], "three":["orange","purple","hotpink"]}',
objects = JSON.parse(jsonObj);
// set timer values
var basetime = 1000;
var delaytime = basetime;
// cache the ul list
var myul = $('<ul/>').appendTo('body');
//process outer objects
$.each(objects, function(key, item) {
// process color array held in item
$.each(item, function(index, color) {
setTimeout(function() {
$('<li/>').text(color).css('color', color).appendTo(myul);
}, delaytime);
delaytime = delaytime + basetime;
});
});
Test it out here https://jsfiddle.net/MarkSchultheiss/yb1w3o73/
var jsonObj = '{"one":["red","green","blue"], "two":["red","green","blue"], "three":["orange","purple","hotpink"]}',
object = JSON.parse(jsonObj),
cOne = object.one,
cTwo = object.two,
cThree = object.three,
i = 0,
timer;
$('body').append('<ul>');
var i = 0;
timer = setInterval(function() {
if (i === cOne.length - 1) clearInterval(timer);
$('body ul').append('<li>');
$('body ul li').last().text(cOne[i]);
i++;
}, 1000);
Related
I have a loop in which I am calling rec_append() recursively, apparently the first pass alone works, then the loop stops.
I have an array of 4 elements going into that $.each loop but I see only the first element going into the function recursively. Help!
I switched it for a element.forEach but that gives me only the second element and I am stuck, is there a better solution to process a tree of elements? My array is a part of a tree.
var data = JSON.parse(JSON.stringify(result))
var graph = $(".entry-point");
function rec_append(requestData, parentDiv) {
var temp_parent_details;
$.each(requestData, function (index, jsonElement) {
if (typeof jsonElement === 'string') {
//Element construction
//Name and other details in the form of a : delimited string
var splitString = jsonElement.split(':');
var details = document.createElement("details");
var summary = document.createElement("summary");
summary.innerText = splitString[0];
details.append(summary);
temp_parent_details = details;
parentDiv.append(details);
var kbd = document.createElement("kbd");
kbd.innerText = splitString[1];
summary.append(' ');
summary.append(kbd);
var div = document.createElement("div");
div.className = "col";
details.append(div);
var dl = document.createElement("dl");
div.append(dl);
var dt = document.createElement("dt");
dt.className = "col-sm-1";
dt.innerText = "Path";
div.append(dt);
var dd = document.createElement("dd");
dd.className = "col-sm-11";
dd.innerText = splitString[2];
div.append(dd);
var dt2 = document.createElement("dt");
dt2.className = "col-sm-1";
dt2.innerText = "Type";
div.append(dt2);
var dd2 = document.createElement("dd");
dd2.className = "col-sm-11";
dd2.innerText = splitString[1];
div.append(dd2);
} else {
$.each(jsonElement, function (jsonElementArrIndx, jsonChildElement) {
rec_append(jsonChildElement, temp_parent_details); //Only 1 pass works, rest skip
});
}
});
}
rec_append(data, graph);
Sample data:enter image description here
https://jsfiddle.net/mr_antlers/ryLtwcbe/
I have put this together. I simple image swap on click. I've repeated the same block of code for each face element I want to swap. So this block repeats for eyes, then nose, mouth etc...
//eyes
var img_eyes = []
img_eyes[0] = "http://guildofone.com/makeneki-neko/img/SVG/eyes0.svg";
img_eyes[1] = "http://guildofone.com/makeneki-neko/img/SVG/eyes1.svg";
img_eyes[2] = "http://guildofone.com/makeneki-neko/img/SVG/eyes2.svg";
//Select all elements on the page with the name attribute equal to VCRImage
var eyes = document.querySelectorAll('[name=eyes]');
for(var i=0; i < eyes.length; i++)
{
var eyes = eyes[i];
eyes.addEventListener('click', eyesClicked(), false);
}
function eyesClicked()
{
var counter = 0;
return function(event)
{
counter++;
this.src = img_eyes[counter % img_eyes.length];
}
}
I'd like to cut down the repetition in the arrays and the click listeners...
Ideally I'd also like a button to toggle each face attribute.I haven't got to this yet. A random button would be nice too. Any help on these would be appreciated.
Many thanks in advance for guidance on improving this code.
Just combine all the similar logic into a function and the parts that differ pass in as parameters. In my example, I am passing in the images and the elements to bind an event too - the rest of the implementation within the functions are the same.
window.onload = function() {
//eyes
var img_eyes = []
img_eyes[0] = "http://guildofone.com/makeneki-neko/img/SVG/eyes0.svg";
img_eyes[1] = "http://guildofone.com/makeneki-neko/img/SVG/eyes1.svg";
img_eyes[2] = "http://guildofone.com/makeneki-neko/img/SVG/eyes2.svg";
//face
var img_face = []
img_face[0] = "http://guildofone.com/makeneki-neko/img/SVG/face0.svg";
img_face[1] = "http://guildofone.com/makeneki-neko/img/SVG/face1.svg";
img_face[2] = "http://guildofone.com/makeneki-neko/img/SVG/face2.svg";
//build the rest of the images
//add the features
addFeature( document.querySelectorAll( "[name=eyes]" ), img_eyes )
addFeature( document.querySelectorAll( "[name=face]" ), img_face )
}
function addFeature( features, imgs ) {
//add the feature
for( var i=0; i < features.length; i++ ) {
var feature = features[i];
feature.addEventListener( "click", featureClicked(), false )
}
function featureClicked() {
let counter = 0;
return function( event ) {
counter++;
this.src = imgs[counter % imgs.length];
}
}
}
A couple of opportunities:
Define features as a list
Populate list of feature values dynamically with new Array(3).fill().map()
Add a listener on the containing element of the image instead of the image itself
Maintain the state of the current index on the DOM element using a data attribute (e.g. data-index)
const features = [
{
element: document.querySelector('.eyes'),
values: new Array(3).fill().map((value, index) => `http://guildofone.com/makeneki-neko/img/SVG/eyes${index}.svg`),
},
{
element: document.querySelector('.noses'),
values: new Array(3).fill().map((value, index) => `http://guildofone.com/makeneki-neko/img/SVG/nose${index}.svg`),
}
];
function initialize() {
features.forEach(feature => {
feature.element.addEventListener('click', () => {
const current = parseInt(feature.element.getAttribute('data-index'), 10);
const nextIndex = (current + 1) % feature.values.length;
feature.element.setAttribute('data-index', nextIndex);
updateFeature(feature);
});
updateFeature(feature);
});
}
function updateFeature(feature) {
const index = feature.element.getAttribute('data-index');
const img = feature.element.querySelector('img');
img.src = feature.values[index];
}
initialize();
<div class="eyes" data-index="0">
<img />
</div>
<div class="noses" data-index="0">
<img />
</div>
I am building an application that takes in grade and then gives the average. It also has a sort button that makes it so you can sort by the last name of the student entered and a clear button to clear the current values that are in the display array. Here is the code I have so far for the javascript file:
var $ = function (id) {return document.getElementById(id);}
"use strict";
var scoreArray = [];
var dispArray = [];
var displayScores = function () {
var totalScore = 0;
var numberOfScores = 0;
var averageScore = 0;
numberOfScores = scoreArray.length;
//loop to find the total score
for (var i=0;i<numberOfScores;i++)
{
totalScore = totalScore + scoreArray[i];
}
//find the average
averageScore = totalScore/numberOfScores;
var st="";
//put the string in the display array
for(var i=0; i<numberOfScores;i++)
{
st += (dispArray[i]+"\n");
}
//display the average score
$("#average_score").val(averageScore.toString());
$("#scores").val(st);
};
$("#add_button").click(function(){
var scoreNumber = parseInt( $("#score").val());
var scoreString = $("#last_name").val() + ", " + $("first_name").val() + ": " + $("#score").val();
scoreArray.push(scoreNumber);
dispArray.push(scoreString);
displayScores();
//reset the values
$("#first_name").val("");
$("#last_name").val("");
$("#score").val("");
$("#first_name").focus();
});
//function to clear the contents of the form
$("#clear_button").click(function(){
//empty the arrays
scoreArray=[];
dispArray=[];
//reset the values in the form
$("#scores").val("");
$("#first_name").val("");
$("#last_name").val("");
$("average_score").val("");
$("#score").val("");
});
//function to sort the scores based on the last name that was entered
$("#sort_button").click(function(){
var mylen = scoreArray.length;
//sorting
for(var kk=0;kk<mylen;kk++)
{
for(var aa = 1; aa<(mylen-kk);aa++)
{
var xp1 = dispArray[aa-1].split(" ");
var lname1 = xp1[0];
lname1 = lname1.slice(0, -1);
var xp2 = dispArray[aa].split(" ");
var lname2 = xp2[0];
lname2 = lname2.slice(0, -1);
if (lname1 > lname2){
var tp1 = scoreArray[aa];
scoreArray[aa]=scoreArray[aa-1];
scoreArray[aa-1] = tp1;
var tp2 = dispArray[aa];
dispArray[aa]=dispArray[aa-1];
dispArray[aa-1] = tp2;
}
}
}
//display the scores
$("#scores").val("");
var st=" ";
for(var i=0;i<dispArray.length;i++)
{
st += (dispArray[i]+"\n");
}
//display the sorted scores
$("scores").val(st);
});
$("#first_name").focus();
It is giving the error: Uncaught TypeError: Cannot read property 'click' of null
at scores.js:38
Line 38 is the click function for the add button to add the score to the display array: $("#add_button").click(function(){
Any thoughts on this?
This is happening because you've defined $ as a function which returns the result of document.getElementById().
What you've not accounted for is what happens if this operation finds no element, in which case it returns null, and there is no .click() method of null.
In short: your selector #add_button seems not to be finding the intended element. So check your DOM, and the presence of the element, before running that line. Always suspect your selectors. Either that or build in a check that only goes to .click() on finding an element successfully.
let el = $('#add_button');
if (el) el.click(...);
A container div.example can have different 1st-level child elements (section, div, ul, nav, ...). Quantity and type of those elements can vary.
I have to find the type (e.g. div) of the direct child that occurs the most.
What is a simple jQuery or JavaScript solution?
jQuery 1.7.1 is available, although it should work in IE < 9 (array.filter) as well.
Edit: Thank you #Jasper, #Vega and #Robin Maben :)
Iterate through the children using .children() and log the number of element.tagNames you find:
//create object to store data
var tags = {};
//iterate through the children
$.each($('#parent').children(), function () {
//get the type of tag we are looking-at
var name = this.tagName.toLowerCase();
//if we haven't logged this type of tag yet, initialize it in the `tags` object
if (typeof tags[name] == 'undefined') {
tags[name] = 0;
}
//and increment the count for this tag
tags[name]++;
});
Now the tags object holds the number of each type of tag that occurred as a child of the #parent element.
Here is a demo: http://jsfiddle.net/ZRjtp/ (watch your console for the object)
Then to find the tag that occurred the most you could do this:
var most_used = {
count : 0,
tag : ''
};
$.each(tags, function (key, val) {
if (val > most_used.count) {
most_used.count = val;
most_used.tag = key;
}
});
The most_used object now holds the tag used the most and how many times it was used.
Here is a demo: http://jsfiddle.net/ZRjtp/1/
Edit: I think a jQuery function like below should be more useful..
DEMO
$.fn.theMostChild = function() {
var childs = {};
$(this).children().each(function() {
if (childs.hasOwnProperty(this.nodeName)) {
childs[this.nodeName] += 1;
} else {
childs[this.nodeName] = 1;
}
});
var maxNode = '', maxNodeCount = 0;
for (nodeName in childs) {
if (childs[nodeName] > maxNodeCount) {
maxNode = nodeName;
maxNodeCount = childs[nodeName];
}
}
return $(maxNode);
}
And then you can,
$('div.example').theMostChild().css('color', 'red');
A function like below should give you the count of child elements, from which you can get the max count. See below,
DEMO
$(function () {
var childs = {};
$('div.example').children().each(function () {
if (childs.hasOwnProperty(this.nodeName)) {
childs[this.nodeName] += 1;
} else {
childs[this.nodeName] = 1;
}
});
for (i in childs) {
console.log(i + ': ' + childs[i]);
}
});
That is not possible without some information about the expected types of child nodes.
EDIT : It is possible as Jasper pointed out that we need not know the tag names before hand. The following works in case you're looking only within a specific set of selectors.
var selectorArray = ['div', 'span', 'p',........]
var matches = $(div).children(selectorArray.join());
var max = 0, result = [];
$.each(selectorArray, function(i, selector){
var l = matches.filter(selector).length;
if(l > max){
max = l;
result[max] = selector;
}
});
result[max] gives you the tag name and max gives you the occurrence count
I have a large array, with non-sequential IDs, that looks something like this:
PhotoList[89725] = new Array();
PhotoList[89725]['ImageID'] = '89725';
PhotoList[89725]['ImageSize'] = '123';
PhotoList[89726] = new Array();
PhotoList[89726]['ImageID'] = '89726';
PhotoList[89726]['ImageSize'] = '234';
PhotoList[89727] = new Array();
PhotoList[89727]['ImageID'] = '89727';
PhotoList[89727]['ImageSize'] = '345';
Etc....
I'm trying to figure out, given an ID, how can I can get the next and previous ID... So that I could do something like this:
<div id="current">Showing You ID: 89726 Size: 234</div>
Get Prev Get Next
Obviously, if we're at the end or beginning of the array we just a message...
Why don't you add properties 'Prev' & 'Next' to that array?
PhotoList[89725] = new Array();
PhotoList[89725]['Prev'] = 89724;
PhotoList[89725]['Next'] = 89726;
PhotoList[89725]['ImageID'] = '89725';
PhotoList[89725]['ImageSize'] = '123';
This is just 'doubly-linked list' data structure.
Based on your example the IDs are sequential...
This is another way of writing your example. new Array() really isn't what you should be using because those are objects you are creating. Also, I left the numbers as strings, but I'm not sure why you would want to do that. You could add next and prev like kuy suggested
PhotoList[89725] = {ImageID: '89725',
ImageSize: '123'};
PhotoList[89725] = {ImageID: '89726',
ImageSize: '234',
Next: '89727',
Prev: '89725'};
PhotoList[89725] = {ImageID: '89727',
ImageSize: '345'};
All of these are accessible just like your other structure.
There's really no way other than to iterate through the possible ids sequentially until you find one which has an entry in your array. For example:
function findClosest(arr, id, increasing) {
var step = increasing ? 1 : -1;
for(var i=id+step; i>=0 && i<=max_id; i+=step)
if( arr[id] )
return id;
}
Obviously, this approach requires that you keep track of the max_id so that you don't iterate forever; here I assume that it's a global variable, but you might want to make it a parameter to the findClosest function. You'd call this function like so:
var prev = findClosest(arr, id, false);
var next = findClosest(arr, id, true);
I agree with the rest quotes you should be using objects not an array. Also make sure you create new arrays using the literal notation and not the new keyword with built in types. The new keyword is bad news and you could clobber the global object. Check out JSLint.
var a = new Array(); //bad dont use
var a = []; //this is the best way to create a new array
var o = {}; //create new objects like this
As for the problem at hand. Why not write a simple container that has its own internal counter?
function PhotoListContainer(PhotoList)
{
if(PhotoList === undefined)
throw("no photo list");
this.counter = 0;
var self = this;
this.current = function(){
return PhotoList[self.counter];
};
this.next = function(){
return PhotoList[self.counter + 1];
};
this.prev = function(){
return PhotoList[self.counter - 1];
};
// You could even write a function that loops each value from the current counter :)
this.each_from_counter = function(callback){
for(var i = self.counter; i < PhotoList.length; i++)
{
callback(PhotoList[i], i);
self.counter++;
}
};
}
//use
var pc = new PhotoListContainer(PhotoList);
pc.counter = 500;
pc.next(); //returns the 501st object
pc.prev(); //returns the 499th object
pc.each_from_counter(function(photo, index){
photo.somehting;
});
No arrays at all are better..
images = {
0: {
size: 12345, /* dont realy need as you can use JS to mesure the size. */
title: "day 1 on holiday"
},
1: {
size: 13549, /* dont realy need as you can use JS to mesure the size. */
title: "day 2 on holiday"
},
2: {
size: 16548, /* dont realy need as you can use JS to mesure the size. */
title: "day 3 on holiday"
},
}
for(x in images){
/* x = "the id of the image." */
url[] = "/images/" + x + ".png";
title[] = images[x].title;
size[] = images[x].size;
console.log("File: " + url[x] + " , Title: " + title[x] + " , Size: " + size + "bytes")
}
var sibNum = 0;
var sibList = [];
var prevSiblingID = false;
for (n in w) {
sibNum++;
sibList[n] = {
title : n,
prevSiblingID : prevSiblingID
};
if (prevSiblingID) {
sibList[prevSiblingID].nextSiblingID = n;
}
prevSiblingID = n;
};
sibList[prevSiblingID].nextSiblingID = false;
you can use grep function and calculate prev or next item of specified array:
object = $.grep(data, function(e) {
if(e.id == yourId) {
return data[data.indexOf(e) + 1]; // or -1 for prev item
}
});
i think your image list will come from DB so you may can try this code, this code is working for me.
<?
$prev="";
$next="";
$cur=0;
$i=0;
$pid=$_GET['pid'];
while($rowcon=mysql_fetch_assoc($result))
{
$arr[$i]=$rowcon['pid'];
if($rowcon['pid']==$pid)
{
$cur=$i;
}
$i++;
}
if($cur<$num_rows)
$next=$arr[$cur+1];
else
$next="";
if($cur>0)
$prev=$arr[$cur-1];
else
$prev="";
echo $prev." ".$cur." ".$next;
?>