How to implement Image reordering in meteor.js - javascript

Need to implement sortable functionality in Image Collection . Used rank value to sort the images .. For all categories it works ..
If images are categorized by category name it doesn't work ..
How to proceed further for different categories
Gallery.html
<div id="grid-container" class="cbp-l-grid-agency grid">
{{#each images getCurrentCategory}}
{{> image}}
{{/each}}
{{> addInfo}}
</div>
Gallery.js
Template.gallery.rendered = function(){
this.$('#grid-container').sortable({
stop: function(e, ui) {
el = ui.item.get(0)
before = ui.item.prev().get(0)
after = ui.item.next().get(0)
if(!before) {
newRank = Blaze.getData(after).rank - 1
} else if(!after) {
newRank = Blaze.getData(before).rank + 1
} else {
newRank = (Blaze.getData(after).rank + Blaze.getData(before).rank)/2
}
Images.update({_id: Blaze.getData(el)._id}, {$set: {rank: newRank}})
}
})
}
Template.gallery.helpers({
'getCurrentCategory': function() {
return Template.instance().currentcategory.get();
},
'images': function (currentcategory) {
if(currentcategory == 'all' || !currentcategory){
return Images.find({},{sort: {rank: 1}});
}
return Images.find({category:currentcategory});
}
});

If I understand your question correctly, you just need to do this:
return Images.find({category:currentcategory},{sort:{rank:1}})
It's the same as your all category cursor but with the added sort specifier.

Related

How to filter undefined values from Array JavaScript?

The code below is working fine it's filtering duplicate values from Array but it's not filtering undefined Values console.log([67, undefined]). i want to filter undefined values and duplicate values from array and output in options values as selected. I would be grateful for any help.
i have two datatable with drag and drop functionality. each row of datatable contain the IDs. when i drag the row of table 1 into table 2. the data stored into the Array. i have the addArray function for push the IDs into an Array there also filtering the duplicate IDs and make it uniqueArray. now i want to create the Select option Element which containing the iDs as a value. I want if i drag out a value from Array then the select options update automatically and delete the option from it...?
js
$(document).ready(function () {
new Sortable(drag, {
group: 'sortables',
multiDrag: true, // Enable multi-drag
selectedClass: 'selected', // The class applied to the selected items
fallbackTolerance: 3, // So that we can select items on mobile
animation: 150,
onUpdate: function (e, tr) {
addArray();
checkFields();
},
});
new Sortable(drop, {
group: "sortables",
multiDrag: true, // Enable multi-drag
selectedClass: 'selected', // The class applied to the selected items
fallbackTolerance: 3, // So that we can select items on mobile
animation: 150,
onChange: function (event, tr) {
Sorting();
addArray();
checkFields();
},
});
function addArray() {
let uniqueArray = [],
html = [];
$('#drop').children().each(function () {
const pk = $(this).data('pk');
if (pk && !uniqueArray.includes(pk)) {
uniqueArray.push(pk);
html.push(`<option value="${pk}">${pk}</option>`);
}
});
$('#id_articles').html(html.join(""))
}
function Sorting() {
sort = [];
$('#drop').children().each(function () {
sort.push({ 'pk': $(this).data('pk'), 'order': $(this).index() })
});
let crf_token = $('[name="csrfmiddlewaretoken"]').attr('value') // csrf token
$.ajax({
url: "/rundown/sorts/",
type: "post",
headers: { "X-CSRFToken": crf_token },
datatype: 'json',
data: {
'sort': JSON.stringify(sort),
},
success: function () {
console.log('success')
}
});
};
function checkFields() {
if ($('#drop tr').length >= 1) {
$('#drop').find('#blank_row').remove();
} else {
$('#drop').append($("<tr>").attr("id", "blank_row")
.append($('<td>').attr("colspan", '4')
.text('Drop here...')));
}
};
});
You do not have a filter. Why not just test
function stop_duplicate_in_array(array, value) {
if (!value && value !== 0) return array; // value is falsy but not 0
if (!array.includes(value)) array.push(value);
return array;
}
if 0 is not a possible value, remove && value !== 0
function stop_duplicate_in_array(array, value) {
if (value && !array.includes(value)) array.push(value);
return array;
}
You can simplify
function addArray() {
let uniqueArray = [],
html = [];
$('#drop').children().each(function() {
const pk = $(this).data('pk');
if (pk && !uniqueArray.includes(pk)) {
uniqueArray.push(pk);
html.push(`<option value="${pk}">${pk}</option>`);
}
});
$('#id_articles').html(html.join(""))
}
addArray()
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="drop">
<article data-pk="A">A</article>
<article>Undefined</article>
<article data-pk="B">B</article>
<article data-pk="">Empty</article>
<article data-pk="C">C</article>
</div>
<select id="id_articles"></select>
Some remarks:
Currently you are adding a value, then removing it again in the stop-function, and adding it yet again. True, the values will be unique, but this is not very efficient. Use a Set for efficiently making a collection unique.
Use jQuery more to the full when creating option elements
To answer your question: just check for undefined before including it. But if you use the Set-way of working (see point 1), you can just delete undefined from that Set
Here is how it could look:
function addArray() {
let articles = new Set(
$('#drop').children().map((i, elem) => $(elem).data('pk')).get()
);
articles.delete(undefined);
$('#id_articles').empty().append(
Array.from(articles, value => $("<option>", { value }).text(value))
);
}
addArray();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="drop">
<div data-pk="red"></div>
<div data-pk="blue"></div>
<div data-pk="red"></div>
<div></div>
</div>
<select id="id_articles"></select>
Simply compare element with undefined
const array = [1,2,undefined,3,4,undefined,5];
const result = array.filter((el) => el !== undefined);
console.log(result)

Load more data using vue js when page is bottom area

I tried to make my Load More data when my page scroll to the bottom. The first thing is I make a div element that I put at the end of the data loop.
<div class="products">
<p>{{ status }}</p>
<div class="product" v-for="(item, index) in items">
<div>
<div class="product-image"><img :src="item.link" alt=""></div>
</div>
<div>
<h4 class="product-title">{{ item.title }}</h4>
<p>Price : {{ price }}</p>
<button class="add-to-cart btn" #click="addItem(index)">Add Item To Cart</button>
</div>
</div>
<div id="product-list-bottom"></div>
</div>
Div element with id product-list-bottom I will detect it using scrollMonitor.js
My default data :
data: {
status: 'Empty product',
total: 0,
items: [],
cart: [],
newSearch: 'anime',
lastSearch: '',
price: STATIC_PRICE,
result: []
}
Inside mounted I detected scroll to bottom :
mounted: function() {
this.onSubmit()
var vueInstance = this
var elem = document.getElementById('product-list-bottom')
var watcher = scrollMonitor.create(elem)
watcher.enterViewport(function() {
vueInstance.appendItems()
})
}
Inside mounted I call onSubmit :
onSubmit: function() {
this.items = ''
this.status = "Searching keyword '" + this.newSearch + "' on server ..."
this.$http.get('/search/'.concat(this.newSearch))
.then(function(response) {
this.lastSearch = this.newSearch,
this.status = 'Find ' + response.data.length + ' data'
this.result = response.data
this.appendItems()
})
}
And inside onSubmit I call appendItems function :
appendItems: function() {
if(this.items.length < this.result.length) {
var start = this.items.length
var end = parseInt(this.items.length + 5)
var append = this.result.slice(start, end)
this.items = this.items.concat(append)
console.log(append)
}
}
All goes well, but when I scroll down I get an error message :
This is because this line :
this.items = this.items.concat(append)
How do I make the data on xxx change (always added five new data from the array) according to the command on the line :
var end = parseInt(this.items.length + 5)
Thanks
it seems '/search/'.concat(this.newSearch) gets evaluated into function and not an actual string value
Try this if you are using babel/webpack
this.$http.get(`/search/`${this.newSearch}`)
Or if not
this.$http.get('/search/' + this.newSearch)
I think since Vue 2.3+ or so you can get this done without any jQuery stuff or any other dependencies:
<style>
.scrollbar{
overflow-y: scroll;
//...
}
.styled-scrollbar::-webkit-scrollbar
.styled-scrollbar::-webkit-scrollbar-thumb
.styled-scrollbar::-webkit-scrollbar-track{
//styling
}
</style>
<template>
//...
<div #scroll="scroll" class="scrollbar">
<div v-for="item in items" :key="item.id">
//TODO: item content
</div
</div>
//...
</template>
<script>
{
data: {
//..
lastScrollUpdate:0
}
//..
mounted: {
scroll:function (e) {
var scrollBar=e.target;
if((scrollBar.scrollTop + scrollBar.clientHeight >= scrollBar.scrollHeight-20)){
var t=new Date().getTime();
if((t-this.lastScrollUpdate)>3000){
this.lastScrollUpdate=t;
console.log('reached end: '+scrollBar.scrollTop+' '+scrollBar.clientHeight+' '+scrollBar.scrollHeight);
//TODO: load more data
}else{
console.log("< 3sec between scoll. no update");
}
}
},
//..
}
}
</script>
You may also want to adjust this to "#scroll.passive", in order to let the scroll-function be executed parallel to the UI (https://v2.vuejs.org/v2/guide/events.html#Event-Modifiers)

Meteor:search-source doesn´t clear the search

I´m having some troubles with Meteor Search-source. The pagackage works fine, but I think that it has big leaks in the documentation. My problem right now is that I can´t clear the search when I don´t type any in the search field.
Currently the App show a list of websites. If I looking for some web in the search field, the App show me a list with results. But when I delete the characters in the field text (empty search), the list with results doesn´t disappear. It show the complete list of elements instead of show a empty list.
I have tried a lot of solutions but nothing works...
You can test typing for example "coursera" in the search field in my app, and next delete all types to check it out.
Some suggestion? Many thanks in advance
My App
SERVER
SearchSource.defineSource('items', function(searchText, options) {
var options = {sort: {upvote: -1}, limit: 20};
// var options = options || {};
if(searchText) {
var regExp = buildRegExp(searchText);
/*var selector = {title: regExp, description: regExp};*/
var selector = {$or: [
{title: regExp},
{description: regExp}
]};
return Websites.find(selector, options).fetch();
} else {
return Websites.find({}, options).fetch();
}
});
function buildRegExp(searchText) {
var words = searchText.trim().split(/[ \-\:]+/);
var exps = _.map(words, function(word) {
return "(?=.*" + word + ")";
});
var fullExp = exps.join('') + ".+";
return new RegExp(fullExp, "i");
}
CLIENTE
//search function
var options = {
keepHistory: 1000 * 60 * 5,
localSearch: true
};
var fields = ['title','description'];
itemSearch = new SearchSource('items', fields, options);
//end search function
//search helper
Template.searchResult.helpers({
getItems: function() {
return itemSearch.getData({
transform: function(matchText, regExp) {
return matchText.replace(regExp, "$&")
},
sort: {upvote: -1}
});
},
isLoading: function() {
return itemSearch.getStatus().loading;
}
});
// search events
Template.searchBox.events({
'keyup #search-box': _.throttle(function(e) {
var text = $(e.target).val().trim();
console.log(text);
itemSearch.search(text,{});
}, 200)
});
HTML
<template name="searchResult">
<div class="container">
<div class="jumbotron searchResult">
<h3> Search results </h3>
<ol>
{{#each getItems}}
{{> website_item_search}}
{{/each}}
</ol>
<!--<div id="search-meta">
{{#if isLoading}}
searching ...
{{/if}}
</div>-->
</div>
</div>
</template>
Just by changing the code on server file, you should be able to see no results on blank text field.
Here is new code. https://github.com/ashish1dev/search_source_example
SearchSource.defineSource('packages', function(searchText, options) {
var options = {sort: {isoScore: -1}, limit: 20};
if(searchText.length>=1) {
var regExp = buildRegExp(searchText);
var selector = {$or: [
{packageName: regExp},
{description: regExp}
]};
return Packages.find(selector, options).fetch();
} else if (searchText.length===0){
return [];// return blank array when length of text searched is zero
}
else {
return Packages.find({}, options).fetch();
}
});

Sort a list of two different objects with one common property

I have two lists that are coming from an API that represent two different classes. I want to display the two lists according to their proprieties in one general list where the elements are sorted by their date.
I need to separate the two lists because the proprieties to display are different and they only share the Date.
I manage to display the two list separately but cannot merge the two... Does anyone have an idea on that? Thank you very much.
here is the .js and view:
.js:
function activityController($http) {
var vm = this;
vm.race= [];
vm.try= [];
vm.errorMessage = "";
vm.isBusy = true;
$http.get("/api/race")
.then(function (response) {
//Sucess
angular.copy(response.data, vm.race);
}, function (error) {
//Failure
vm.errorMessage = "Failed to load the data" + error;
})
$http.get("/api/try")
.then(function (response) {
//Sucess
angular.copy(response.data, vm.try);
}, function (error) {
//Failure
vm.errorMessage = "Failed to load the data" + error;
})
.finally(function () {
vm.isBusy = false;
});
}
View :
<div class="col-md-offset-7">
<div class="text-danger" ng-show="vm.errorMessage">{{ vm.errorMessage}}</div>
<wait-cursor ng-show="vm.isBusy"></wait-cursor>
<ul class="well" ng-repeat="activities in vm.race| orderBy: 'date':true">
<li> {{activities.title}}</li>
<li>Date : {{activities.date | date :'dd-MM-yyyy'}}</li>
<li>Temps : {{activities.time}}</li>
</ul>
<ul class="well" ng-repeat="activities in vm.try| orderBy: 'date':true">
<li> {{activities.person}}</li>
<li>Date : {{activities.date | date :'dd-MM-yyyy'}}</li>
<li>Temps : {{activities.type}}</li>
</ul>
The way I would do it is:
<ul class="well" ng-repeat="activities in vm.getRacesAndTrys()| orderBy: 'date':true">
<li> {{activities.title}}</li>
<li>Date : {{activities.date | date :'dd-MM-yyyy'}}</li>
<li>Temps : {{activities.time}}</li>
</ul>
With a function in your vm:
vm.getRacesAndTrys = function() {
var result = [];
for (var i = 0; i < vm.try.length; i++) {
var item = vm.try[i];
result.push({
title: item.person,
date: item.date,
time: item.type
});
}
for (var i = 0; i < vm.race.length; i++) {
result.push(vm.race[i]);
}
return result;
}
This will create a combined array and also convert all the try objects so that their fields match that of the race objects.

How to render nested collections in Meteor?

Summary:
Child categories nested inside of Parent Categories are not getting rendered in a Meteor template.
Details:
Consider a data model for 'Category' as such:
// Model Schema
Category {
idCategory : 20, (id of the category itself)
idCategoryParent : 0, (idCategory of our parent category)
defaultLabel : "Movies" (our label)
}
There are parent categories and child categories. Parent categories have an idCategoryParent property value of 0. Child categories store the idCategory of their parents as their idCategoryParent property. I'm trying to loop through a collection of these Categories and render them in the following way:
<b>Movies</b> // parent category is in bold
<ul> // child categories are rendered as an unordered list
<li>Horror</li>
<li>Comedy</li>
<li>Action</li>
<li>Drama</li>
</ul>
<b>Music</b>
<ul>
<li>Rock</li>
<li>Classical</li>
<li>Ambient</li>
</ul>
However, this is what I actually get:
<b>Movies</b>
<ul> // empty...
</ul>
<b>Music</b>
<ul>
</ul>
Source Code:
// How we get the 'categories_parents' data
Template.content.categories_parents = function (){
/*
* Get all parent categories (categories with an idCategoryParent of 0)
*/
var parents = Categories.find({idCategoryParent:0});
var pCount = parents.count();
for (var i = 0; i < pCount; i++){
var pId = parents.db_objects[i]['idCategory'];
/*
* Get all child categories of the parent (categories with
* an idCategoryParent equal to value of parent category's idCategory).
*/
var children = Categories.find({idCategoryParent:pId});
var cCount = children.count();
/*
* Assign the child categories array as a property of the parent category
* so that we can access it easily in the template #each expression
*/
parents.db_objects[i]['children'] = children;
}
return parents;
}
// This is our template
<template name="content">
<h1>Categories</h1>
{{#each categories_parents}}
<b>{{defaultLabel}}</b><br />
<ul>
{{#each children}}
<li>{{defaultLabel}}</li>
{{/each}}
</ul>
{{/each}}
</template>
Other template configurations I have tried in troubleshooting:
{{#each children}}
<li>A Child Exists Here</li> // Even this never rendered... no children?
{{/each}}
Any clues as to why this is happening would be appreciated.
Your model is kind of iffy... Consider
{name:"Category name", parent:"_id of parent category"}
Okay, that's a lot simpler. To create a category.
var moviesId = Categories.insert({name:"Movies"});
Categories.insert({name:"Horror",parent:moviesId});
That was easy enough. Now, to render in a way that {{#each}} works:
Template.categories.categories = function(parent) {
if (parent) {
return Categories.find({parent:parent}).fetch();
} else {
return Categories.find({parent:{$exists:false}});
}
}
You might be seeing where this is going...
<template name="categories">
{{#each categories}}
<ul>{{name}}
{{#each categories _id}}
<li>{{name}}</li>
{{/each}}
</ul>
{{/each}}
</template>
Now I'm not sure if the {{#each}} block helper can take a function argument when it calls another helper. If it doesn't...
Template.categories.categories = function() {
return Categories.find({parent:{$exists:false}}).map(function(parentCategory) {
return _.extend(parentCategory,
{children:Categories.find({parent:parentCategory._id}).fetch()});
});
}
That's a real doozy. It returns parent categories with a new "children" list property, that contains all the children categories. Now you can do:
<template name="categories">
{{#each categories}}
<ul>{{name}}
{{#each children}}
<li>{{name}}</li>
{{/each}}
</ul>
{{/each}}
</template>
Clever, eh?
I don't know about db_objects, when I try and access that property on a cursor (which is what find() returns), it's null.
You could fetch the items that matches your query instead, and then do your iteration:
Template.content.categories_parents = function (){
var parents = Categories.find({idCategoryParent:0}).fetch(); // Returns an array.
for (var i = 0; i < parents.length; i++){
var pId = parents[i]['idCategory'];
var children = Categories.find({idCategoryParent:pId});
// No need for array here, cursor is fine.
parents.db_objects[i]['children'] = children;
}
return parents;
}
I'm new at this myself, so maybe there's a more efficient way of doing it, but I don't know it currently.
Update after Eric's comment.
The js file looks like this:
Categories = new Meteor.Collection("categories");
if (Meteor.isClient) {
Template.categories.categories = function () {
var parents = Categories.find({idCategoryParent:0}).fetch();
for (var i = 0; i < parents.length; i += 1) {
var pId = parents[i]['idCategory'];
var children = Categories.find({idCategoryParent:pId});
parents[i]['children'] = children;
}
return parents;
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
Categories.remove({});
var data = [
{idCategoryParent: 0, idCategory: 1, label: "Movies"},
{idCategoryParent: 1, idCategory: 0, label: "Science Fiction"},
{idCategoryParent: 1, idCategory: 0, label: "Drama"},
{idCategoryParent: 0, idCategory: 2, label: "Music"},
{idCategoryParent: 2, idCategory: 0, label: "Jazz"},
{idCategoryParent: 2, idCategory: 0, label: "Piano"}
];
for (var i = 0; i < data.length; i += 1) {
Categories.insert(data[i]);
}
});
}
The html file looks like this:
<head>
<title>nested_template</title>
</head>
<body>
{{> categories}}
</body>
<template name="categories">
<h1>Categories</h1>
{{#each categories}}
<b>{{label}}</b>
<ul>
{{#each children}}
<li>{{label}}</li>
{{/each}}
</ul>
{{/each}}
</template>
It works just fine for me.
Solved.
My solution was to remove the {{#each}} logic from the template and replace it with a single handlebars helper expression, and pass back the needed html all at once. The html is generated from data in the cursor of the Categories collection.
Not so sure about having all this html in my logic though -- is this bad design? If so I'll defer to a better answer.
// This is the new template
<template name="content">
<h1>Categories</h1>
{{listCategories}}
</template>
// Handlebars helper
Handlebars.registerHelper('listCategories', function() {
var parents = Categories.find({idCategoryParent:0});
var countParents = parents.count();
var string = '';
// iterate over each parent category
for(m = 0; m < countParents; m++){
// Get the parents category id
var pId = parents.db_objects[m].idCategory;
var children = Categories.find({idCategoryParent:pId});
var count = children.count();
/*
* Build the Parent Category html
* Example: <b>Movies</b><ul>
*/
string = string + '<b>' + parents.db_objects[m].defaultLabel + '</b><ul>';
// iterate over each child category
for(var i = 0; i < count; i++){
/*
* Build the child category html
* Example: <li>Horror</li>
*/
string = string + '<li>' + children.db_objects[i]['defaultLabel'] + '</li>';
}
// Close up the unordered list
string = string + '</ul>';
}
// Return the string as raw html
return new Handlebars.SafeString(string);
});
// Rendered out the result correctly like so:
<b>Movies</b>
<ul>
<li>Horror</li>
<li>Comedy</li>
<li>Action</li>
<li>Drama</li>
</ul>
<b>Music</b>
<ul>
<li>Rock</li>
<li>Classical</li>
<li>Ambient</li>
</ul>

Categories