Meteor JS: Use subset of same subscribed data - javascript

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();
});

Related

Binding an array of objects to their specific form fields for updating/deleting

I need to store form data into an array of objects as a link. Then be able to click the link and fill the form with the objects/data to update or delete.
I'm able to store the form data as objects in an array, but can't figure out how to load it back into the form to update.
var actors = [];
var addActor = function() {
// Assigning Form values to variables
var firstN = $("#fName").val();
var lastN = $("#lName").val();
var gender = $("[name='gender']:checked").val();
var birthdate = $("#birthDate").val();
var action = $("#action").prop('checked');
var comedy = $("#comedy").prop('checked');
var drama = $("#drama").prop('checked');
var sciencefiction = $("#sciencefiction").prop('checked');
var horror =$("#horror").prop('checked');
var suspense = $("#suspense").prop('checked');
// creates newActor variable that contains an object for each input value
var newActor = {fName: firstN, lName: lastN, gender: gender, birthDate: birthdate, action: action, comedy: comedy, drama: drama, suspense: suspense, sciencefiction: sciencefiction, horror: horror}
$("#actorsTable").append("<tr><td><a href='' class='update'>" + newActor.fName + " " + newActor.lName + "</a></td></tr> ");
actors.push(newActor);
console.log(actors);
};
Now my selector function grabs the object but I just don't know how to load it into the form to update and delete. I've been stuck on this for a few days now.
var selectActor = function(e) {
e.preventDefault();
var rowClicked = $(this).parent().parent();
row = rowClicked.index();
alert (actors[row].fName + " " + actors[row].lName + " " + actors[row].gender + " " + actors[row].birthDate + " " + actors[row].action + " " + actors[row].comedy + " " + actors[row].drama + " " + actors[row].suspense + " " + actors[row].sciencefiction + " " + actors[row].horror);
console.log(actors[row]);
};
Here is what I have in action so far. When I check console everything is correct with storing, and selecting, but I can't find anything that shows how to store objects into their respected form fields.
Codepen
Consider using a namespace for your code, then create some generic functions for object manipulations (like an array) as well as some specific to your form.
Note that some libraries like angular, react etc. handle some of this for you, but you asked for the manual part, and it might also be worth some study on one way to do it.
Here is an updated sample to play with: http://codepen.io/MarkSchultheiss/pen/LNqdxK?editors=0010
var myApp = myApp || {};
myApp.arrayObj = {
indexOf: function(myArray, searchTerm, property) {
for (var i = 0; i < myArray.length; i++) {
if (myArray[i][property] === searchTerm) return i;
}
return -1;
},
indexAllOf: function(myArray, searchTerm, property) {
var ai = [];
for (var i = 0; i < myArray.length; i++) {
if (myArray[i][property] === searchTerm) ai.push(i);
}
return ai;
},
lookup: function(myArray, searchTerm, property, firstOnly) {
var found = [];
var i = myArray.length;
while (i--) {
if (myArray[i][property] === searchTerm) {
found.push(myArray[i]);
if (firstOnly) break; //if only the first
}
}
return found;
},
lookupAll: function(myArray, searchTerm, property) {
return this.lookup(myArray, searchTerm, property, false);
},
remove: function(myArray, searchTerm, property, firstOnly) {
for (var i = myArray.length - 1; i >= 0; i--) {
if (myArray[i][property] === searchTerm) {
myArray.splice(i, 1);
if (firstOnly) break; //if only the first term has to be removed
}
}
},
removeByIndex: function(myArray, index) {
myArray.splice(index, 1);
}
};
myApp.func = {
hasDuplicates: function(actor) {
var allLast = myApp.arrayObj.lookup(myApp.data.actors, actor.lName, "lName", false);
var allFirst = myApp.arrayObj.lookup(allLast, actor.fName, "fName", true);
return !!allFirst.length;
},
appendActorRow: function(newActor) {
myApp.data.actorsTable.append("<tr><td><a href='' class='update' data-actorid='" + newActor.actorId + "'>" + newActor.fName + " " + newActor.lName + "</a></td></tr>");
},
getActor: function() {
var newActor = {
fName: $("#fName").val(),
lName: $("#lName").val(),
gender: $("input[type=radio][name='gender']:checked").val(),
birthDate: $("#birthDate").val(),
action: $("#action").prop('checked'),
comedy: $("#comedy").prop('checked'),
drama: $("#drama").prop('checked'),
suspense: $("#suspense").prop('checked'),
sciencefiction: $("#sciencefiction").prop('checked'),
horror: $("#horror").prop('checked'),
actorId: $("#fName").data('actorid')
}
return newActor;
},
putActor: function(actor) {
$("#fName").val(actor.fName);
$("#lName").val(actor.lName);
$("input[type=radio][name='gender']").val(actor.gender);
$("#birthDate").val(actor.birthDate);
$("#action").prop('checked', actor.action);
$("#comedy").prop('checked', actor.comedy);
$("#drama").prop('checked', actor.drama);
$("#suspense").prop('checked', actor.suspense);
$("#sciencefiction").prop('checked', actor.sciencefiction);
$("#horror").prop('checked', actor.horror);
$("#fName").data('actorid', actor.actorId);
},
addActor: function(allowDuplicates) {
var newActor = myApp.func.getActor();
var validActor = false;
if (!allowDuplicates && !myApp.func.hasDuplicates(newActor)) {
validActor = true;
}
if (!validActor && allowDuplicates) {
validActor = true;
}
if (validActor) {
myApp.data.lastActorId = myApp.data.lastActorId + 1;
newActor.actorId = myApp.data.lastActorId;
myApp.func.appendActorRow(newActor);
myApp.data.actors.push(newActor);
}
return newActor;
},
updateRowByIndex: function(actor, index) {
myApp.data.actorsTable.eq(index).html(actor.fName + " " + actor.lName).data("actorid", actor.actorId).addClass('update');
},
updateRowByActorId: function(actor, actorId) {
var r = myApp.data.actorsTable.find('a[data-actorid="' + actorId + '"]');
r.html(actor.fName + " " + actor.lName).data("actorid", actor.actorId).addClass('update');
},
clearForm: function() {
$('#fName').val("");
$('#lName').val("");
$('#birthDate').val("");
$('#form').find('input[type="checkbox"]').prop("checked", false);
$('#form').find('input[type="radio"]').prop("checked", false);
return this;
},
selectActor: function(e) {
e.preventDefault();
var selectActorId = $(this).data('actorid');
var actor = myApp.arrayObj.lookup(myApp.data.actors, selectActorId, "actorId", true)[0];
myApp.func.putActor(actor);
myApp.func.setButtons("old")
},
updateActor: function() {
var actor = myApp.func.getActor();
var index = myApp.arrayObj.indexOf(myApp.data.actors, actor.actorId, "actorId", true);
if (index != -1) {
myApp.data.actors[index] = actor;
myApp.func.updateRowByActorId(actor, actor.actorId);
}
},
deleteActor: function() {
var actor = myApp.func.getActor();
var index = myApp.arrayObj.indexOf(myApp.data.actors, actor.actorId, "actorId", true);
if (index != -1) {
var r = myApp.data.actorsTable.find('a[data-actorid="' + actor.actorId + '"]');
r.parents('tr').remove();
// either will work, used the index one
// myApp.arrayObj.remove(myApp.data.actors, actor.actorId, "actorId", true);
myApp.arrayObj.removeByIndex(myApp.data.actors, index);
}
myApp.func.clearForm().setButtons("new");
// myApp.func.setButtons("new");
},
setButtons: function(foo) {
// if page is new only or form is being filled with new data
// show 'Add Actor' button only
$("#addNewActor").toggle((foo === "new"));
$("#updateActor").toggle(!(foo === "new"));
$("#deleteActor").toggle(!(foo === "new"));
}
};
myApp.data = {
actors: [],
actorsTable: $("#actorsTable"),
lastActorId: 0
};
/* end of myApp */
// Function checks state of page and shows/hides buttons
var actorStatex = function(foo) {
// if page is new only or form is being filled with new data
// show 'Add Actor' button only
$("#addNewActor").toggle((foo === "new"));
$("#updateActor").toggle(!(foo === "new"));
$("#deleteActor").toggle(!(foo === "new"));
};
var validateForm = function(e) {};
$(document).ready(function() {
$('#results').on('click', '.update', myApp.func.selectActor);
$("#birthDate").datepicker();
myApp.func.setButtons("new");
$("#addNewActor").on('click', function() {
var addedActor = myApp.func.addActor(false);
});
$("#updateActor").on('click', myApp.func.updateActor);
$("#deleteActor").on('click', myApp.func.deleteActor);
$("#clearButton").on('click', function() {
myApp.func.clearForm();
myApp.func.setButtons("new");
});
});
it's because the names of your attributes in your alert doesn't match with those in your newActor object.
You should use alert(actors[row].fName) instead of alert(actors[row].fname)
By the way you could make it really simplier using your form id #actorForm
It should be something like this (I have not tested)
var actors = [], index = 0;
$("#addNewActor").click(function() {
var newActor = $('#actorForm').serialize();
$("#actorsTable").append("<tr><td><a href='' class='update'>" + newActor.fName + " " + newActor.lName + "</a></td></tr> ");
actors.push(newActor);
});
// select actor
$(document).on('click', '#actorsTable tr', function() {
if(actors[$(this).index]) {
index = $(this).index();
var actor = actors[index];
// populate your form
}
});
$("#updateActor").click(function() {
var newActor = $('#actorForm').serialize();
actors[index] = newActor;
});
$("#deleteActor").click(function() {
actors.splice(index, 1);
});

Related posts on my blog (javascript)

So I bought a template and hosting from ghost.org. Unfortunately, it uses handlebars, a language I'm not very familiar with. I figured it out, but then when I implemented this jquery file for adding related posts to the bottom of the page using tags created in handlebars, it did not work. When I looked over the file it seemed to be fine. So I'm wondering if any of you can find a bug or error with the prototype based programming. Credits to dane cando at github for the file, but it isnt working. Below are the javascript for the related posts function and the ul class from the handlebars file.
JavScript File
(function($) {
defaults = {
feed: '/rss',
titleClass: '.post-title',
tagsClass: '.post-meta',
limit: 5,
debug: true,
template: '<li>{title}</li>',
messages: {
noRelated: 'No related posts were found.'
}
};
function RelatedPosts(element, options) {
this.element = element;
this.options = $.extend({}, defaults, options);
this.parseRss();
};
RelatedPosts.prototype.displayRelated = function(posts) {
var self = this, count = 0;
this._currentPostTags = this.getCurrentPostTags(this.options.tagsClass);
var related = this.matchByTag(this._currentPostTags, posts), options = this.options;
related.forEach(function(post) {
var template = options.template.replace(/{[^{}]+}/g, function(key) {
return post[key.replace(/[{}]+/g, '')] || '';
});
if (count < self.options.limit) {
$(self.element).append($(template));
}
count++;
});
if (count == 0) {
$(this.element).append($('<li>' + this.messages.noRelated + '</li>'));
}
};
RelatedPosts.prototype.parseRss = function(pageNum, prevId, feeds) {
var page = pageNum || 1, prevId = prevId || '', feeds = feeds || [], self = this;
$.ajax({
url: this.options.feed + '/' + page,
type: 'GET'
})
.done(function(data, textStatus, xhr) {
var curId = $(data).find('item > guid').text();
if (curId != prevId) {
feeds.push(data);
self.parseRss(page + 1, curId, feeds);
}
else {
var posts = self.getPosts(feeds);
self.displayRelated(posts);
}
})
.fail(function(e) {
self.reportError(e);
});
};
RelatedPosts.prototype.getCurrentPostTitle = function(titleClass) {
if (titleClass[0] != '.') {
titleClass = '.' + titleClass;
}
var postTitle = $(titleClass).text();
if (postTitle.length < 1) {
this.reportError("Couldn't find the post title with class: " + titleClass);
}
return postTitle;
};
RelatedPosts.prototype.getCurrentPostTags = function(tagsClass) {
if (tagsClass[0] != '.') {
tagsClass = '.' + tagsClass;
}
var tags = [];
$(tagsClass + ' a').each(function() {
tags.push($(this).text());
});
if (tags.length < 1) {
this.reportError("Couldn't find any tags in this post");
}
return tags;
};
RelatedPosts.prototype.getPosts = function(feeds) {
var posts = [], items = [];
feeds.forEach(function(feed) {
items = $.merge(items, $(feed).find('item'));
});
for (var i = 0; i < items.length; i++) {
var item = $(items[i]);
if (item.find('title').text() !== this.getCurrentPostTitle(this.options.titleClass)) {
posts.push({
title: item.find('title').text(),
url: item.find('link').text(),
content: item.find('description').text(),
tags: $.map(item.find('category'), function(elem) {
return $(elem).text();
})
});
}
}
if (posts.length < 1) {
this.reportError("Couldn't find any posts in feed: " + feed);
}
return posts;
};
RelatedPosts.prototype.reportError = function(error) {
if (this.options.debug) {
$(this.element).append($('<li>' + error + '</li>'));
}
};
RelatedPosts.prototype.matchByTag = function(postTags, posts) {
var matches = [];
posts.forEach(function(post) {
var beenAdded = false;
post.tags.forEach(function(tag) {
postTags.forEach(function(postTag) {
if (postTag.toLowerCase() === tag.toLowerCase() && !beenAdded) {
matches.push(post);
beenAdded = true;
}
});
});
});
if (matches.length < 1) {
this.reportError("There are no closely related posts");
}
return matches;
};
$.fn.ghostRelated = function(options) {
return this.each(function() {
new RelatedPosts(this, options);
});
};
})(jQuery);
$('.related-posts').ghostRelated();
HTML List
<ul class="related-posts">
</ul>

Is it Possible/Okay to have two controller files for one view in meteor?

so heres my folder structure for the client:
https://s3.amazonaws.com/f.cl.ly/items/0I0S063e3U0A2o2s3k21/Image%202014-12-05%20at%206.42.17%20PM.png
The problem is I have two states for tournaments... One Live and One noLive.
The use all the exact same views ect but could potentially have very different functionality.
Is there a trick were I can use two completely different controllers for the same view based on the data the view needs to load in iron router or something?
-thanks
For reference here is my:
routes.js for tourneys:
/* Tournaments / Browse section */
Router.route('/tournaments/:_id', function () {
this.fastRender = true;
// add the subscription handle to our waitlist
this.wait(Meteor.subscribe('campaigns'));
// this.ready() is true if all items in the wait list are ready
// console.log("Tournaments.findOne({_id: this.params._id}:", Campaigns.findOne({_id: this.params._id}));
if (this.ready()) {
this.render('tournament', {
data: function () {
return Campaigns.findOne({_id: this.params._id});
}
});
} else {
this.render('loading');
}
});
tournaments.js:
/* Globals */
Template.tournament.rendered = function () {
var self = this;
var participants = $('.participant-id');
var currentParticipant;
var nextRound;
var thisMatch;
var nextMatch;
var bracket;
participants.map(function(index, value){
if ($(value).text() === Meteor.userId()) {
if ($(value).parent().find('.participant-status').text() === 'undetermined') {
nextRound = $(value).parent().find('.participant-round').text();
thisMatch = $(value).parent().find('.participant-match').text();
bracket = $(value).parent().parent().parent().find('.participant');
};
};
});
nextRound = parseInt(nextRound) + 1;
nextMatch = Math.round(parseInt(thisMatch)/2) - 1;
if (parseInt(thisMatch) % 2 != 0) {
currentParticipant = 0;
}else{
currentParticipant = 1;
}
var winnerOptions = '';
var winnerBox = $('<div class="select-winner">');
bracket.map(function(index, value) {
winnerOptions += '<span class="winner-option"> '+$(value).find('.participant-title').text()+' <div class="winner-info"> '+$(value).find('a').html()+' </div> </span>'
});
winnerBox.append(winnerOptions);
$($($('.round'+nextRound).find('li')[nextMatch]).find('.participant')[currentParticipant]).removeClass('loser').addClass('undetermined');
$($($('.round'+nextRound).find('li')[nextMatch]).find('.participant')[currentParticipant]).find('a').addClass('tooltip').html(winnerBox);
var tournamentStartTime = function(){
var d = new Date();
var n = d.getTime();
var currentTime = TimeSync.serverTime(n);
var startTime = self.data.card.startTime;
var difference = startTime - currentTime;
var hoursDifference = Math.floor(difference/1000/60/60);
difference -= hoursDifference*1000*60*60
var minutesDifference = Math.floor(difference/1000/60);
difference -= minutesDifference*1000*60
var secondsDifference = Math.floor(difference/1000);
/* if ends (make tournament live server side?) */
if (hoursDifference < 0 || minutesDifference < 0 || secondsDifference < 0) {
Meteor.clearInterval(tStartTime);
Session.set("tournamentStartTime", false);
}else{
if (hoursDifference < 10) {hoursDifference = "0"+hoursDifference;}
if (minutesDifference < 10) {minutesDifference = "0"+minutesDifference;}
if (secondsDifference < 10) {secondsDifference = "0"+secondsDifference;}
var formattedTime = hoursDifference + ':' + minutesDifference + ':' + secondsDifference;
Session.set("tournamentStartTime", formattedTime);
}
};
Session.set("tournamentStartTime", '00:00:00');
tournamentStartTime();
var tStartTime = Meteor.setInterval(tournamentStartTime, 1000);
};
Template.tournament.events({
// Select winner from 2 options in tooltip
// Then previous round is given winner class on correct person
'click .winner-option': function(event){
// var self = $(event.target)
// var winner = self.text()
// self.parent().hide()
// self.closest('.participant').removeClass('undetermined')
// self.parent().siblings('.participant-title').text(winner)
// var classes = self.closest('ul').prev().attr('class')
// $('.' + classes.substring(0, classes.indexOf(' ')) + ' .participant-title').each(function() {
// if ($(this).text() === winner) {
// $(this).parent().parent().removeClass('loser').addClass('winner')
// }
// // else {
// // $(this).parent().parent().removeClass('winner').addClass('loser')
// // }
// });
// // $(.previousULClass .
$('#theaterMode').show();
}
});
Template.tournament.helpers({
round: function() {
var tournament = this.tournament.brackets;
var rounds = tournament.length;
var results = [];
tournament.map(function(value, index){
var currentRound = index + 1;
results.push({rounds: rounds, currentRound: currentRound, matches: value});
});
// console.log("results:", results);
return results;
},
match: function(){
// console.log("matches:", this.matches);
return this.matches;
},
participant: function(){
var results = [];
// console.log("this:", this);
this.map(function (value, index) {
// console.log("value, index:", value, index);
var type = value['win'];
var obj = {
id: value['id'],
rank: value['id'].slice(0,3),
displayName: value['displayName'],
thisRound: value['round'],
thisMatch: value['match'],
status: type
};
if (type === true || type === 'undetermined') {
obj.winner = true;
}else{
obj.loser = true;
}
results.push(obj);
});
// console.log("results:", results);
return results;
},
tournamentStartTime: function(){
return Session.get('tournamentStartTime');
}
});
How do you recognize which state is current? You should post some code, routes.js, tournament.js and your view.blade, for better understanding what you really wanna do and for figure out, what the best pratice is. :)

Mixitup.js and multiple filtering

I've taken the code from http://codepen.io/patrickkunka/pen/iwcap and modified it, but i can't make it work as desired.
If i select only 1 filter, it works fine - but if i start compare my filters, it's not returning the correct result.
I'm returning the "filters" dynamicly - but that shouldn't be the problem.
Can anyone give me some heads up on this?
Link for js fiddle :
http://jsfiddle.net/u6ksLwts/26/
// Loop through all div's with .mix class
var genders = [];
var models = [];
var brands = [];
var prices = [];
$(".mix").each(function () {
addToArrayIfNew(genders, $(this).attr('data-Gender'));
addToArrayIfNew(models, $(this).data('model'));
addToArrayIfNew(brands, $(this).data('brand'));
if ($(this).data('brand').match(/\s/g)){
$(this).addClass('Brand_' + $(this).data('brand'));
}
addToArrayIfNew(prices, $(this).data('price'));
// Fix invalid css class names
});
// Now return the arrays to HTML code
if (models.length > 0) {
var filterName = 'Model';
var idName = 'ModelsFilter';
$("#" + idName).append(RenderHTMLFilterBoxes(filterName, models));
}
if (genders.length > 0) {
var filterName = 'Gender';
var idName = 'GendersFilter';
$("#" + idName).append(RenderHTMLFilterBoxes(filterName, genders));
}
if (brands.length > 0) {
var filterName = 'Brand';
var idName = 'BrandsFilter';
$("#" + idName).append(RenderHTMLFilterBoxes(filterName, brands));
}
function RenderHTMLFilterBoxes(filterName, arraylist) {
var htmlStr = "";
for (var i in arraylist) {
htmlStr = htmlStr + '<div class="filterBoxes"><div class="checkbox">';
htmlStr = htmlStr + '<input type="checkbox" value=".' + filterName + '_' + arraylist[i].replace(/[^a-zA-Z]+/g,'') + '" />';
htmlStr = htmlStr + '<label>' + arraylist[i] + '</label>';
htmlStr = htmlStr + '</div></div>';
}
return htmlStr;
}
function addToArrayIfNew(arr, item) {
if (item && jQuery.inArray(item, arr) == -1) {
arr.push(item);
}
}
function RenderHTMLPriceRange() {
var lowest = Number.POSITIVE_INFINITY;
var highest = Number.NEGATIVE_INFINITY;
var tmp;
for (var i = prices.length - 1; i >= 0; i--) {
tmp = prices[i];
if (tmp < lowest) lowest = tmp;
if (tmp > highest) highest = tmp;
}
console.log(highest, lowest);
}
// MIX IT UP CODE
// To keep our code clean and modular, all custom functionality will be contained inside a single object literal called "checkboxFilter".
var checkboxFilter = {
// Declare any variables we will need as properties of the object
$filters: null,
$reset: null,
groups: [],
outputArray: [],
outputString: '',
// The "init" method will run on document ready and cache any jQuery objects we will need.
init: function () {
var self = this; // As a best practice, in each method we will asign "this" to the variable "self" so that it remains scope-agnostic. We will use it to refer to the parent "checkboxFilter" object so that we can share methods and properties between all parts of the object.
self.$filters = $('#Filters');
self.$reset = $('#Reset');
self.$container = $('#Container');
self.$filters.find('.filterBoxes').each(function () {
self.groups.push({
$inputs: $(this).find('input'),
active: [],
tracker: false
});
});
// console.log(self.groups);
self.bindHandlers();
},
// The "bindHandlers" method will listen for whenever a form value changes.
bindHandlers: function () {
var self = this;
self.$filters.on('change', function () {
self.parseFilters();
});
self.$reset.on('click', function (e) {
e.preventDefault();
self.$filters[0].reset();
self.parseFilters();
});
},
// The parseFilters method checks which filters are active in each group:
parseFilters: function () {
var self = this;
// loop through each filter group and add active filters to arrays
for (var i = 0, group; group = self.groups[i]; i++) {
group.active = []; // reset arrays
group.$inputs.each(function () {
$(this).is(':checked') && group.active.push(this.value);
});
group.active.length && (group.tracker = 0);
}
// console.log(self.groups);
self.concatenate();
},
// The "concatenate" method will crawl through each group, concatenating filters as desired:
concatenate: function () {
var self = this,
cache = '',
crawled = false,
checkTrackers = function () {
console.log(1);
var done = 0;
for (var i = 0, group; group = self.groups[i]; i++) {
(group.tracker === false) && done++;
}
return (done < self.groups.length);
},
crawl = function () {
// console.log(2);
for (var i = 0, group; group = self.groups[i]; i++) {
group.active[group.tracker] && (cache += group.active[group.tracker]);
if (i === self.groups.length - 1) {
self.outputArray.push(cache);
cache = '';
updateTrackers();
}
}
},
updateTrackers = function () {
//console.log(3);
for (var i = self.groups.length - 1; i > -1; i--) {
var group = self.groups[i];
if (group.active[group.tracker + 1]) {
group.tracker++;
break;
} else if (i > 0) {
group.tracker && (group.tracker = 0);
} else {
crawled = true;
}
}
};
self.outputArray = []; // reset output array
do {
crawl();
}
while (!crawled && checkTrackers());
self.outputString = self.outputArray.join();
// If the output string is empty, show all rather than none:
!self.outputString.length && (self.outputString = 'all');
//console.log(self.outputString);
// ^ we can check the console here to take a look at the filter string that is produced
// Send the output string to MixItUp via the 'filter' method:
if (self.$container.mixItUp('isLoaded')) {
// console.log(4);
self.$container.mixItUp('filter', self.outputString);
// console.log(self.outputString);
}
}
};
// On document ready, initialise our code.
$(function () {
// To avoid non javascript browsers not to see the content, the display:none will first be set
// here, instead of the CSS file
// Initialize checkboxFilter code
checkboxFilter.init();
// Instantiate MixItUp
$(".mix").css("display", "none");
$('#Container').mixItUp({
load: {
filter: 'none'
},
controls: {
toggleLogic: 'or',
toggleFilterButtons: true,
enable: true // we won't be needing these
},
animation: {
easing: 'cubic-bezier(0.86, 0, 0.07, 1)',
duration: 600
}
});
});
HTML :
<div id="Filters">
<div id="GendersFilter"></div>
<div id="BrandsFilter"></div>
<div id="ModelsFilter"></div>
</div>
<div id="Container">
<div class="mix Brand_MystBrandname Model_ Gender_0" data-Gender="0" data-Brand="My 1st. Brand name!" data-Model="" data-Price="1000">My 1st. Brand name!</div>
<div class="mix Brand_Casio Model_ Gender_0" data-Gender="0" data-Brand="Casio" data-Model="" data-Price="10">My casio block</div>
<div class="mix Brand_Seiko Model_ Gender_2" data-Gender="2" data-Brand="Seiko" data-Model="Precision" data-Price="200">My seiko block</div>
<div class="mix Brand_Nikon Model_Lada Gender_1" data-Gender="1" data-Brand="Nikon" data-Model="Lada" data-Price="40">My Nikon block</div>
<div class="mix Brand_DELL Model_2 Gender_Inspirion" data-Gender="1" data-Brand="DELL" data-Model="Inspirion" data-Price="500">My Dell block</div>
</div>
<script src="http://cdn.jsdelivr.net/jquery.mixitup/latest/jquery.mixitup.min.js"></script>
self.$filters.find('.filterBoxes').each(function () {
self.groups.push({
$inputs: $(this).find('input'),
active: [],
tracker: false
});
});
changed to
> self.$filters.find('.filters').each(function () {
> self.groups.push({
> $inputs: $(this).find('input'),
> active: [],
> tracker: false
> });
> });
and added css class "filters" on each of the groups
<div id="Filters">
<div id="GendersFilter" class="filters"></div>
...
</div>

ko foreach with ko.computed is coping the input value to next input

I have an observable string witch contains a list of options.
Every single option is separated by this symbol "-*!*-"
There is also a computed function called optionsSplitted which is responsible to return an array of options.
This array is used from the foreach binding.
There is also a button to add options.
Everything works fine on the model, I can edit my options, add a new one.
But when I add some options and then edit one, it will be copied to the next one. Why???
jsfiddle
function ViewModel(args) {
var self = this;
self.activeAttributes = ko.observable({
options: ko.observable('a-*!*-b-*!*-c')
});
self.activeAttributes.optionsSplitted = ko.computed(function(){
return self.activeAttributes().options().split("-*!*-");
});
self.changed = function (data) {
var options = "", optionsSize = $('.option').length;
$('.option').each(function(i){
if(i < optionsSize - 1)
options += $(this).val() + "-*!*-";
else
options += $(this).val();
});
self.activeAttributes().options(options);
alert("Options: " + options)
};
self.addOption = function(data) {
self.activeAttributes().options(self.activeAttributes().options() + "-*!*-");
};
}
var model = {
};
var viewModel = new ViewModel(model);
ko.applyBindings(viewModel);
Using the ko.utils.arrayMap utility is fine.
jsfiddle
function ViewModel(args) {
var self = this;
self.activeAttributes = ko.observable({
options: ko.observable('a-*!*-b-*!*-c')
});
self.activeAttributes.optionsSplitted = ko.computed(function(){
var options = self.activeAttributes().options().split("-*!*-");
return ko.utils.arrayMap(options, function (option) {
return {
value: ko.computed({
read: function () { return option; }
})
};
});
});
self.changed = function (data) {
var options = "", optionsSize = $('.option').length;
$('.option').each(function(i){
if(i < optionsSize - 1)
options += $(this).val() + "-*!*-";
else
options += $(this).val();
});
self.activeAttributes().options(options);
alert("Options: " + options)
};
self.addOption = function(data) {
self.activeAttributes().options(self.activeAttributes().options() + "-*!*-");
};
};
var model = {
};
var viewModel = new ViewModel(model);
ko.applyBindings(viewModel);

Categories