jQuery .each() multiplying "i"? - javascript

I'm trying to make a kind of newswire for a school project but I'm having a few problems with jQuery's .each() function. I'm trying to find a way to skip every 2nd array element in a loop.
Basically I have data from a NY Times API and got both title and abstract and push these into an array that I then loop and animate every once and awhile.
My problem is, I can't seem to find a way to get Title + Abstract (Index[0]+[1]) without the loop just moving to index[1] again. Now I knows in Javascript you can simply use a for (i=0; i < array.length; i+2) and thus skip every 2nd array element, but I haven't had any luck incorporating that. Any suggestions? :)
$(document).ready(function() {
var newsWire = [];
function loadNewswire() {
return $.getJSON('http://api.nytimes.com/svc/news/v3/content/all/all.json',
{'api-key': 'XXXXXXXXXXXXXXXXXXX'},
function(data) {
console.log(data)
var newsWireTemp = [];
for (var i = 0; i < data.results.length; i++) {
var breakingNews = data.results[i];
var breakingTitle = breakingNews.title.toUpperCase();
var breakingAbstract = breakingNews.abstract;
newsWireTemp.push(breakingTitle);
newsWireTemp.push(breakingAbstract);
}
newsWire = newsWireTemp;
});
}
loadNewswire().done(function () {
var items = newsWire;
$text = $('#newswiretxt span'),
delay = 10; //seconds
function loop (delay) {
$.each(items, function (i, elm){
$text.delay(delay*1E3).fadeOut();
$text.queue(function(){
$text.html(items[i]+ ": " +items[i+1]);
$text.dequeue();
});
$text.fadeIn();
$text.queue(function(){
if (i == items.length -1) {
loop(delay);
}
$text.dequeue();
});
});
}
console.log(items.length);
loop(delay);
});
});

Basically, just push the desired text concatenated into the array for the load function. Then as you iterate you can simply write the contents as is without messing with the iteration.
$(document).ready(function() {
var newsWire = [];
function loadNewswire() {
return $.getJSON('http://api.nytimes.com/svc/news/v3/content/all/all.json',
{'api-key': 'XXXXXXXXXXXXXXXXXXX'},
function(data) {
console.log(data)
var newsWireTemp = [];
for (var i = 0; i < data.results.length; i++) {
var breakingNews = data.results[i];
var breakingTitle = breakingNews.title.toUpperCase();
var breakingAbstract = breakingNews.abstract;
newsWireTemp.push(breakingTitle + ': ' + breakingAbstract);
}
newsWire = newsWireTemp;
});
}
loadNewswire().done(function () {
var items = newsWire;
$text = $('#newswiretxt span'),
delay = 10; //seconds
function loop (delay) {
$.each(items, function (i, elm){
$text.delay(delay*1E3).fadeOut();
$text.queue(function(){
$text.html(items[i]);
$text.dequeue();
});
$text.fadeIn();
$text.queue(function(){
if (i == items.length -1) {
loop(delay);
}
$text.dequeue();
});
});
}
console.log(items.length);
loop(delay);
});
});

See if this SO thread helps you.
From what I understand, you'd like to skip every other iteration, so checking i's parity to skip when appropriate should work.
For the lazy:
$.each(array, function(index, item) {
if(index % 2 === 0) return true; // This would skip
// Other logic
});
Let me know if it helps or not.

Instead of using two array indexes, use one object, var bn={};, add the two entries, bn.breakingTitle=breakingNews.title.toUpperCase(); and bn.breakingAbstract=breakingNews.abstract; then one push newsWireTemp.push(bn); so each entry in newsWire is more like newsWire[i].breakingTitle and newsWire[i].breakingAbstract.

One way to do it:
Fiddle: http://jsfiddle.net/q18dv4wr/
HTML:
<div id="test1">odds:</div>
<div id="test2">evens:</div>
JS:
var someData = [0,1,2,3,4,5,6,7,8,9,10];
var div1 = $('#test1');
var div2 = $('#test2');
$.each(someData,
function (index, value) {
if (index % 2 == 0) {
return;
}
else {
div1.append(' ' + value);
}
}
);
$.each(someData,
function (index, value) {
if (index % 2 != 0) {
return;
}
else {
div2.append(' ' + value);
}
}
);
EDIT: Seems I posted a moment too late. Someone else gave same idea already. =] Oh well.

You could do this:
$text.html(items[i]+ ": " +items[(i+=1)]);
But personally, I would push the breakingNews object into the array instead of having a different index for each property:
$(document).ready(function() {
var newsWire = [];
function loadNewswire() {
return $.getJSON('http://api.nytimes.com/svc/news/v3/content/all/all.json',
{'api-key': 'XXXXXXXXXXXXXXXXXXX'},
function(data) {
console.log(data)
var newsWireTemp = [];
for (var i = 0; i < data.results.length; i++) {
newsWireTemp.push(data.results[i]);
}
newsWire = newsWireTemp;
});
}
loadNewswire().done(function () {
var items = newsWire;
$text = $('#newswiretxt span'),
delay = 10; //seconds
function loop (delay) {
$.each(items, function (i, elm){
$text.delay(delay*1E3).fadeOut();
$text.queue(function(){
$text.html(items[i].title.toUpperCase()+ ": " +items[i].abstract);
$text.dequeue();
});
$text.fadeIn();
$text.queue(function(){
if (i == items.length -1) {
loop(delay);
}
$text.dequeue();
});
});
}
console.log(items.length);
loop(delay);
});
});

Try using .append() , checking if items[i + 1] is defined before appending items[i + 1] , else return empty string
$text.append(items[i] + (!!items[i+1] ? ":" + items[i+1] + " ": ""))
var items = "abcdefg".split("")
$.each(items, function(i, item) {
$("body").append(items[i] + (!!items[i+1] ? ":" + items[i+1] + " ": ""))
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

Related

jQuery loop vs vanilla JS loop: break/continue/return [duplicate]

Can anybody tell me why the loop did not stop after the 5 entry?
http://jsbin.com/ucuqot/edit#preview
$(document).ready(function()
{
someArray = new Array();
someArray[0] = 't5';
someArray[1] = 'z12';
someArray[2] = 'b88';
someArray[3] = 's55';
someArray[4] = 'e51';
someArray[5] = 'o322';
someArray[6] = 'i22';
someArray[7] = 'k954';
var test = findXX('o322');
});
function findXX(word)
{
$.each(someArray, function(i)
{
$('body').append('-> '+i+'<br />');
if(someArray[i] == 'someArray')
{
return someArray[i]; //<--- did not stop the loop!
}
});
}
Because when you use a return statement inside an each loop, a "non-false" value will act as a continue, whereas false will act as a break. You will need to return false from the each function. Something like this:
function findXX(word) {
var toReturn;
$.each(someArray, function(i) {
$('body').append('-> '+i+'<br />');
if(someArray[i] == word) {
toReturn = someArray[i];
return false;
}
});
return toReturn;
}
From the docs:
We can break the $.each() loop at a particular iteration by making the
callback function return false. Returning non-false is the same as a
continue statement in a for loop; it will skip immediately to the next
iteration.
modified $.each function
$.fn.eachReturn = function(arr, callback) {
var result = null;
$.each(arr, function(index, value){
var test = callback(index, value);
if (test) {
result = test;
return false;
}
});
return result ;
}
it will break loop on non-false/non-empty result and return it back, so in your case it would be
return $.eachReturn(someArray, function(i){
...
here :
http://jsbin.com/ucuqot/3/edit
function findXX(word)
{
$.each(someArray, function(i,n)
{
$('body').append('-> '+i+'<br />');
if(n == word)
{
return false;
}
});
}
Try this ...
someArray = new Array();
someArray[0] = 't5';
someArray[1] = 'z12';
someArray[2] = 'b88';
someArray[3] = 's55';
someArray[4] = 'e51';
someArray[5] = 'o322';
someArray[6] = 'i22';
someArray[7] = 'k954';
var test = findXX('o322');
console.log(test);
function findXX(word)
{
for(var i in someArray){
if(someArray[i] == word)
{
return someArray[i]; //<--- stop the loop!
}
}
}
"We can break the $.each() loop at a particular iteration by making
the callback function return false. Returning non-false is the same as
a continue statement in a for loop; it will skip immediately to the
next iteration."
from http://api.jquery.com/jquery.each/
Yea, this is old BUT, JUST to answer the question, this can be a bit simpler:
function findXX(word) {
$.each(someArray, function(index, value) {
$('body').append('-> ' + index + ":" + value + '<br />');
return !(value == word);
});
}
$(function() {
someArray = new Array();
someArray[0] = 't5';
someArray[1] = 'z12';
someArray[2] = 'b88';
someArray[3] = 's55';
someArray[4] = 'e51';
someArray[5] = 'o322';
someArray[6] = 'i22';
someArray[7] = 'k954';
findXX('o322');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
A bit more with comments:
function findXX(myA, word) {
let br = '<br />';//create once
let myHolder = $("<div />");//get a holder to not hit DOM a lot
let found = false;//default return
$.each(myA, function(index, value) {
found = (value == word);
myHolder.append('-> ' + index + ":" + value + br);
return !found;
});
$('body').append(myHolder.html());// hit DOM once
return found;
}
$(function() {
// no horrid global array, easier array setup;
let someArray = ['t5', 'z12', 'b88', 's55', 'e51', 'o322', 'i22', 'k954'];
// pass the array and the value we want to find, return back a value
let test = findXX(someArray, 'o322');
$('body').append("<div>Found:" + test + "</div>");
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
NOTE: array .includes() may better suit here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
Or just .find() to get that https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
Rather than setting a flag, it could be more elegant to use JavaScript's Array.prototype.find to find the matching item in the array. The loop will end as soon as a truthy value is returned from the callback, and the array value during that iteration will be the .find call's return value:
function findXX(word) {
return someArray.find((item, i) => {
$('body').append('-> '+i+'<br />');
return item === word;
});
}
const someArray = new Array();
someArray[0] = 't5';
someArray[1] = 'z12';
someArray[2] = 'b88';
someArray[3] = 's55';
someArray[4] = 'e51';
someArray[5] = 'o322';
someArray[6] = 'i22';
someArray[7] = 'k954';
var test = findXX('o322');
console.log('found word:', test);
function findXX(word) {
return someArray.find((item, i) => {
$('body').append('-> ' + i + '<br />');
return item === word;
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

How can I iterate up and down a large javascript array in chunks?

I'm trying to write a function that takes a large array and iterates up and down it in a set number of chunks via a previous and next button. I have the next button working fine but cannot get it to reverse the array the same way I go forward. Here's what I have:
Javscript
success: function(data) {
var body = data;
console.log(body.length);
//body/data is a string
var text = body.split(' ');
text.chunk = 0; text.chunkSize = 15;
var next = true;
var increment = function(array,next) {
if (array.chunk < array.length) {
var slice = array.slice(
array.chunk,
Math.min(array.chunk + array.chunkSize, array.length));
var chunk = slice.join(" ");
if (next) {
array.chunk += array.chunkSize;
$( '#test' ).html('<p>' + chunk + '</p>');
}
else {
var slice = array.slice(
array.chunk,
Math.min(array.chunk+array.chunkSize, array.length));
array.chunk -= array.chunkSize;
$( '#test' ).html(chunk);
}
}
}
$("#prev").click(function() {
increment(text);
});
$("#button").click(function() {
increment(text, next);
});
}
success: function(data) {
var body = data;
console.log(body.length);
//body/data is a string
var text = body.split(' ');
text.chunk = 0; text.chunkSize = 15;
var increment = function(array,next) {
if(next) {
array.chunk = Math.min(array.chunk + array.chunkSize, array.length);
} else {
array.chunk = Math.max(array.chunk - array.chunkSize, 0);
}
var slice = array.slice(
array.chunk,
Math.min(array.chunk + array.chunkSize, array.length));
var chunk = slice.join(" ");
}
$("#prev").click(increment(text,false));
$("#button").click(increment(text, true));
}
Is this what you need? Fastly coded, and without testing so use with caution.
Okay, so first of all I really suggest breaking up your code. It looks like this is a response back from a server. I would in the response from the server, parse the data just like you are (side note, why don't you just return json from the server?) but use a call back to handle pagination.
var returnData = data.split(' ');
addPagination(returnData);
Under addPagination I would handle the DOM manipulation:
function addPagination(array) {
$('#container').show();
var incremental = incrementArray(array);
var responseSpan = $('#response');
$('#previous').click(function() {
incremental.previous();
showText();
});
$('#next').click(function() {
incremental.next();
showText();
});
showText();
function showText() {
responseSpan.text(incremental.array.join(', '));
}
}
But to handle the actual shifting of the array, I would use some function outside of your own. This uses Object Oriented JavaScript, so it is a bit more complex. It stores the original array in memory, and has 2 methods (next, previous), and has 1 attribute (array):
function incrementArray(array) {
_increment = 2;
var increment = _increment < array.length ? _increment : array.length;
var index = -2;
this.next = function () {
var isTopOfArray = index + increment > array.length - increment;
index = isTopOfArray ? array.length - increment : index + increment;
this.array = array.slice(index, index + increment);
return this.array;
};
this.previous = function () {
var isBottomOfArray = index - increment < 0;
index = isBottomOfArray ? 0 : index - increment;
this.array = array.slice(index, index + increment);
return this.array;
};
this.next();
return this;
}
To test it, use this jsFiddle.

Meteor JS: Use subset of same subscribed data

I built out a custom pagination script to display data for my app. It works wonderfully. However, I am having a slight problem when it comes to trying to figure out how to grab a subset of the same paginated subscription.
Meteor.startup(function(){
Session.setDefault('page', 1);
Session.setDefault('recordCount', 0);
Session.setDefault('recordsPerPage', 10);
Session.setDefault('currentIndustry', null);
Session.setDefault('currentMapArea', null);
Session.setDefault('gmapLoaded', false);
});
Deps.autorun(function () {
Meteor.call('getJobsCount', Session.get('currentIndustry'), Session.get('currentMapArea'), function (err, count) {
Session.set('recordCount', count);
});
Meteor.subscribe('pagedRecords', Session.get('page'), Session.get('recordsPerPage'), Session.get('currentIndustry'), Session.get('currentMapArea'));
});
Template.gmap.rendered = function() {
if(!Session.get('gmapLoaded'))
gmaps.initialize();
}
var templateName = "jobs";
function plotCities(jobs) {
var addresses = _.chain(jobs)
.countBy('address')
.pairs()
.sortBy(function(j) {return -j[1];})
.map(function(j) {return j[0];})
.slice(0, 99)
.value();
gmaps.clearMap();
$.each(_.uniq(addresses), function(k, v){
var addr = v.split(', ');
Meteor.call('getCity', addr[0].toUpperCase(), addr[1], function(error, city){
if(city) {
var opts = {};
opts.lng = city.loc[1];
opts.lat = city.loc[0];
opts.population = city.pop;
opts._id = city._id;
gmaps.addMarker(opts);
}
});
});
}
Template[templateName].helpers({
selected: function(){
return Session.get('recordsPerPage');
}
});
Template[templateName].pages = function() {
var numPages = Math.ceil(Session.get('recordCount') / Session.get('recordsPerPage'));
var currentPage = Session.get('page');
var totalPages = Session.get('recordCount');
var prevPage = Number(currentPage) - 1;
var nextPage = Number(currentPage) + 1;
var html = '<div class="pagination-cont"><ul class="pagination">';
if (numPages !== 1) {
if (currentPage > 1) {
html += '<li>«</li>';
}
for (var i = currentPage; (i <= numPages) && (i - currentPage < 4); i++) {
if (i < 1) continue;
if (i !== currentPage)
html += '<li>' + i + '</li>';
else
html += '<li class="active">' + i + '</li>';
}
if (currentPage < numPages) {
html += '<li>»</li>';
}
}
html += '</ul></div>';
return html;
}
Template[templateName].jobs = function() {
var options = {};
var cursor;
if(!Session.get('currentMapArea')) {
cursor = Jobs.find({}, {limit: 500});
plotCities(cursor.fetch());
}
return Jobs.find({}, { limit: Session.get('recordsPerPage') });
}
Template[templateName].rendered = function(){
var select = $('#perPage');
var option = select.attr('_val');
$('option[value="' + option + '"]').attr("selected", "selected");
select.selectpicker({
style: 'btn-info col-md-4',
menuStyle: 'dropdown-inverse'
});
}
Template[templateName].events({
'click div.select-block ul.dropdown-menu li': function(e){
var selectedIndex = $(e.currentTarget).attr("rel");
var val = $('select#perPage option:eq(' + selectedIndex + ')').attr('value');
var oldVal = Session.get('recordsPerPage');
if(val != oldVal)
Session.set('recordsPerPage', Number(val));
},
'click .pageNum': function(e){
e.preventDefault();
var num = $(e.currentTarget).data('page');
Session.set('page', Number(num));
}
});
Currently, by default, only 10 records per page show up (unless the user selects from a drop-down a different amount). I have a plotCities function that I am using to try to plot the top 100 cities from the subset that is returned, however, I can't grab the top 100 because only 10 at a time show up.
Is there anyway to do what I am describing?
Ok, so the jobsPerCity and jobs are two totally different things, so I would use a separate on-fly-collection for the first one. Nothing will be stored in the database but the client will "think" that there is actually a jobsPerCity collection, which you can use to plot your map. The way you can achieve this is to define another named collection:
// only on the client !!!
var jobsPerCity = new Meteor.Collection("jobsPerCity");
On the server you will need to define a custom publish method:
Meteor.publish('jobsPerCity', function (options) {
var self = this;
var cities = new Meteor.Collection(null);
var jobToCity = {};
handle1 = Jobs.find({/* whatever condition you want */}).observeChanges({
added: function (id, fields) {
jobToCity[id] = fields.address.split(',')[0].toUpper();
cities.upsert({ _id: jobToCity[id] }, { $inc: { jobsCount: 1 } });
},
removed: function (id) {
cities.upsert({ _id: jobToCity[id] }, { $inc: { jobsCount: -1 } });
delete jobToCity[id];
},
changed: function (id, fields) {
// left as an exercise ;)
},
});
handle2 = cities.find({}, {sort: {jobsCount: -1}, limit: 100}).observeChanges({
added: function (id, fields) {
self.added('jobsPerCity', id, fields);
},
changed: function (id, fields) {
self.changed('jobsPerCity', id, fields);
},
removed: function (id) {
self.removed('jobsPerCity', id);
},
});
self.ready();
self.onStop(function () { handle1.stop(); handle2.stop(); });
});
and your good to go :)
EDIT (simple solution for more static data)
If the data is not going to be updated very often (as #dennismonsewicz suggested in one of his comments), the publish method can be implemented in a much simpler way:
Meteor.publish('jobsPerCity', function (options) {
var self = this, jobsPerCity = {};
Jobs.find({/* whatever condition you want */}).forEach(function (job) {
var city = job.address.split(',')[0].toUpper();
jobsPerCity[city] = jobsPerCity[city] !== undefined ? jobsPerCity[city] + 1 : 1;
});
_.each(jobsPerCity, function (jobsCount, city) {
self.added('jobsPerCity', city, { jobsCount: jobsCount });
});
self.ready();
});

How do I reference the "title" given the id?

var data = {};
data.event = [
{
"id":"998",
"title":"Foo",
"thumb":"",
"source":""
},
{
"id":"999",
"title":"Bar",
"thumb":"",
"source":""
}
]
Given that id=998 I need to extract the value of the "title" and I'm a bit lost as to the proper syntax.
You can iterate with $.each() and check to see if the ID matches, and then write the value of title to a variable.
var title;
$.each(data.event, function(i,e) {
if (this.id==='998') {
title=this.title;
return false;
}
});
FIDDLE
function titleFromId(id) {
for (var i = 0, l = data.event.length; i < l; i += 1) {
if (data.event[i].id === id) {
return data.event[i].title;
}
}
}
var title = titleFromId('998');
You need to loop over the event array. For each item, if item.id is the value you are looking for, then return item.title.
Something like the following:
function findTitleById(desiredId) {
var title, item;
for (var i = data.event.length - 1; i >= 0; i--){
item = data.event[i];
if (item.id === desiredId) {
title = item.title;
break;
}
}
return title;
}
There are more advanced ways to do this, but I would understand the above before attempting them.
You can use $.each() function:
$.each(data.event, function(i, v){
alert(v.id + " " + v.title)
})
http://jsfiddle.net/NGALP/

Port small function from jQuery to Prototype?

For once I need to use Prototype instead of jQuery, which I'm not very comfortable with.
Can someone help me convert this following script:
var output={};
$('ul li').each(function(i,v){
var l=$(this).text().substring(0,1).toUpperCase();
if(typeof(output[l])=="undefined") output[l]=[];
output[l].push($(this).text());
});
$('ul').empty();
for(var i in output){
$('ul').append('<li><p>'+i+'</p></li>');
for(var j in output[i]){
$('ul').append('<li>'+output[i][j]+'</li>');
}
}
Full source: http://jsfiddle.net/flxfxp/3LwH8/7/
Many thanks!
var output = $H({});
$$("ul li").each(function(el){
var l = el.innerHTML.substr(0,1).toUpperCase();
if(typeof(output.get(l))=="undefined") output.set(l, []);
output.get(l).push(el.innerHTML);
})
$$('ul').invoke("update", '');
output.keys().each(function(key){
var values = output.get(key);
$$('ul').first().insert('<li><p>'+key+'</p></li>');
values.each(function(v){
$$('ul').first().insert('<li>'+v+'</li>');
});
});
Tested with latest Prototype
My Prototype is a little rusty but:
var output = $$('ul li').reduce({}, function(rv, li) {
var l = li.innerHTML.substring(0, 1).toUpperCase();
(rv[l] = rv[l] || []).push(li.innerHTML);
return rv;
});
$$('ul').update($(output).collect(function(list, letter) {
return '<li><p>' + letter + '</p></li>' + list.collect(function(txt) {
return '<li>' + txt + '</li>';
}).join('');
}).join(''));
The only thing I'm worried about is that .innerHTML isn't the same as .text() in jQuery, but I don't know how to do .text() in Prototype.
document.observe("dom:loaded", function () {
var output = {};
$$('ul.wikiext-taggedpages li').each(function(v, i) {
var l = v.innerHTML.substring(0, 1).toUpperCase();
if (typeof(output[l]) == "undefined") {
output[l] = [];
}
output[l].push(v.innerHTML);
});
console.log('output', output);
$$('ul.wikiext-taggedpages')[0].update('');
for (var i in output) {
if (output.hasOwnProperty(i)) {
$$('ul.wikiext-taggedpages')[0].insert('<li><p>' + i + '</p></li>');
for (var j in output[i]) {
if (output[i].hasOwnProperty(j)) {
$$('ul.wikiext-taggedpages')[0].insert('<li>' + output[i][j] + '</li>');
}
}
}
}
});

Categories