I'm trying to build an MeteorJS App. This is a kind of playlist manager.
It looks like spotify, There is 3 colums so I made 3 templates,
from left to right :
- The playlists list
- The medias of the selected playlist
- The selected media's details
so here's my code :
server.js
Playlists = new Meteor.Collection("playlists");
if (Meteor.isServer) {
Meteor.startup(function() {
if (Playlists.find().count() === 0) {
var names = ["test1",
"222",
"test3",
"444",
"test5",
"666"];
for (var i = 0; i < names.length; i++)
Playlists.insert({'name': names[i]});
}
return (
Meteor.methods({
addPlaylist: function(playlistName) {
Playlists.insert({'name': playlistName, 'medias': []});
},
delPlaylist: function(playlistId) {
Playlists.remove({'_id': playlistId});
},
renPlaylist: function(playlistId, newName) {
Playlists.update({'_id': playlistId}, {'$set': {'name': newName}});
},
addMedia: function(playlistId, newMedia) {
var current_playlist = Playlists.findOne({'_id': playlistId});
Playlists.update(current_playlist, {$push: {'medias': newMedia}});
}
})
);
});
Playlists.allow({
insert: function(userId, doc) {
return (false);
},
update: function(userId, doc, fields, modifier) {
return (false);
},
remove: function(userId, doc) {
return (false);
}
});
}
my templates :
<template name="t_playlistsCol">
<button id="newPlaylist" class="btn btn-primary" type="button">
New Playlist
</button>
<div id="listPlaylist">
{{#each playlists}}
<button class="playlist {{Pselected}} btn btn-default" type="button">
{{name}}
</button>
{{/each}}
</div>
</template>
<template name="t_mediasCol">
{{#if current_playlist}}
<div class="menuPlaylist">
<img id="playPlaylist" class="btn" src="icon_play1.png">
<h4>{{current_playlist}}</h4>
<div id="optionsPlaylist">
<button id="insertMedia" class="btn btn-primary">Insert</button>
<button id="renamePlaylist" class="btn btn-info">Rename</button>
<button id="deletePlaylist" class="btn btn-danger">Delete</button>
</div>
</div>
<table id="listMedias">
<thead>
<tr>
<th class="mediaName">Titre</th>
<th class="mediaType">Type</th>
<th class="mediaTime">Durée</th>
</tr>
</thead>
<tbody>
{{#each medias}}
<tr class="media {{Mselected}}">
<td class="mediaName">{{name}}</td>
<td class="mediaType">{{type}}</td>
<td class="mediaTime">{{time}}</td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
<div class="menuPlaylist">
<h3 id="noPlaylist">Select a playlist</h3>
</div>
{{/if}}
</template>
<template name="t_previewCol">
{{#if current_media}}
<div class="menuPreview">
<p>{{current_media}}</p>
</div>
{{else}}
<div class="menuPreview">
<h3 id="noPreview">Select a media</h3>
</div>
{{/if}}
</template>
client.js :
(
Playlists = new Meteor.Collection("playlists");
if (Meteor.isClient) {
Template.t_playlistsCol.playlists = function() {
return (Playlists.find({}, {sort: {"name": 1}}));
};
Template.t_playlistsCol.events = {
'click #newPlaylist': function() {
var playlistName = prompt("Nom de la playlist : ");
if (playlistName) {
Meteor.call('addPlaylist', playlistName);
}
},
'click .playlist': function() {
Session.set("selected_playlist", this._id);
}
};
Template.t_playlistsCol.Pselected = function() {
return ( Session.equals("selected_playlist", this._id) ? "Pselected" : '' );
};
Template.t_mediasCol.current_playlist = function() {
var playlist = Playlists.findOne(Session.get("selected_playlist"));
return (playlist && playlist.name);
};
Template.t_mediasCol.medias = function() {
var current_playlist = Playlists.findOne(Session.get("selected_playlist"));
return ( current_playlist.medias );
};
Template.t_mediasCol.events = {
'click #insertMedia': function() {
var playlistId = Session.get("selected_playlist");
var newMedia = {
name: "mediaTest",
type: "img",
time: "00:15"
};
newMedia._id = new Meteor.Collection.ObjectID();
Meteor.call('addMedia', playlistId, newMedia);
},
'click #deletePlaylist': function() {
var playlistId = Session.get("selected_playlist");
Meteor.call('delPlaylist', playlistId);
},
'click #renamePlaylist': function() {
var playlist = Playlists.findOne(Session.get("selected_playlist"));
var newName = prompt("Renommer " + playlist.name + " en :");
if (newName) {
Meteor.call('renPlaylist', playlist._id, newName);
}
},
'click .media': function() {
Session.set("selected_media", this._id);
}
};
Template.t_mediasCol.Mselected = function() {
return ( Session.equals("selected_media", this._id) ? "Mselected" : "" );
};
Template.t_previewCol.current_media = function() {
var playlistId = Session.get("selected_playlist");
var mediaId = Session.get("selected_media");
var playlist = Playlists.find({'_id': playlistId});
return(playlist.medias[0] && playlist.medias[0].name);
};
Accounts.ui.config({
passwordSignupFields: 'USERNAME_ONLY'
});
}
I'm a newbie, I'm learning Meteor for 3 weeks.
SO, MY PROBLEM is that, I want the user to be able to select a media and see its details with the template "t_previewCol" .
It works for the playlists, but I don't have what I want with the helper "Template.t_previewCol.current_media", it prints this error when I do a console.log to debug :
Exception in template helper:
Template.t_previewCol.current_media#http://localhost:3000/client
/client.js?7212665c7dc29cf721272fa4cabeb499f2e18bf5:69:5
bindToCurrentDataIfIsFunction/<#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:2448:7
Blaze.wrapCatchingExceptions/<#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1552:7
Spacebars.call#http://localhost:3000/packages
/spacebars.js?8717c3bee1160f47e7a46ea4e1bd0796f944cad8:169:5
#http://localhost:3000/client
/template.client.js?d2b9a924f64621cd0bcfc1292538f5b63523959a:117:5
Blaze.If/</<#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:2275:11
viewAutorun/<#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1805:7
Blaze.withCurrentView#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:2038:5
viewAutorun#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1804:5
Deps.Computation.prototype._compute#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:214:5
Deps.Computation#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:148:5
Deps.autorun#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:362:7
Blaze.View.prototype.autorun#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1803:7
Blaze.If/<#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:2277:1
fireCallbacks#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1818:9
Deps.nonreactive#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:382:5
Blaze._fireCallbacks/<#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1815:5
Blaze.withCurrentView#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:2038:5
Blaze._fireCallbacks#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1814:3
Blaze.materializeView#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1830:3
.visitObject#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1458:7
.visit#http://localhost:3000/packages
/htmljs.js?fcf2660be84fbc0c33b97ee8932dbd46612f3566:116:7
doMaterialize#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1862:13
Deps.nonreactive#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:382:5
doRender#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1860:7
viewAutorun/<#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1805:7
Blaze.withCurrentView#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:2038:5
viewAutorun#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1804:5
Deps.Computation.prototype._compute#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:214:5
Deps.Computation#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:148:5
Deps.autorun#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:362:7
Blaze.View.prototype.autorun#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1803:7
Blaze.materializeView/<#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1851:5
Deps.nonreactive#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:382:5
Blaze.materializeView#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1850:3
.visitObject#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1458:7
.visit#http://localhost:3000/packages
/htmljs.js?fcf2660be84fbc0c33b97ee8932dbd46612f3566:116:7
.visitArray#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1363:7
.visit#http://localhost:3000/packages
/htmljs.js?fcf2660be84fbc0c33b97ee8932dbd46612f3566:114:9
.visitTag#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1441:9
.visit#http://localhost:3000/packages
/htmljs.js?fcf2660be84fbc0c33b97ee8932dbd46612f3566:101:11
.visitArray#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1363:7
.visit#http://localhost:3000/packages
/htmljs.js?fcf2660be84fbc0c33b97ee8932dbd46612f3566:114:9
.visitTag#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1441:9
.visit#http://localhost:3000/packages
/htmljs.js?fcf2660be84fbc0c33b97ee8932dbd46612f3566:101:11
.visitArray#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1363:7
.visit#http://localhost:3000/packages
/htmljs.js?fcf2660be84fbc0c33b97ee8932dbd46612f3566:114:9
doMaterialize#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1862:13
Deps.nonreactive#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:382:5
doRender#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1860:7
viewAutorun/<#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1805:7
Blaze.withCurrentView#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:2038:5
viewAutorun#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1804:5
Deps.Computation.prototype._compute#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:214:5
Deps.Computation#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:148:5
Deps.autorun#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:362:7
Blaze.View.prototype.autorun#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1803:7
Blaze.materializeView/<#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1851:5
Deps.nonreactive#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:382:5
Blaze.materializeView#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1850:3
.visitObject#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1458:7
.visit#http://localhost:3000/packages
/htmljs.js?fcf2660be84fbc0c33b97ee8932dbd46612f3566:116:7
.visitArray#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1363:7
.visit#http://localhost:3000/packages
/htmljs.js?fcf2660be84fbc0c33b97ee8932dbd46612f3566:114:9
doMaterialize#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1862:13
Deps.nonreactive#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:382:5
doRender#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1860:7
viewAutorun/<#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1805:7
Blaze.withCurrentView#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:2038:5
viewAutorun#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1804:5
Deps.Computation.prototype._compute#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:214:5
Deps.Computation#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:148:5
Deps.autorun#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:362:7
Blaze.View.prototype.autorun#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1803:7
Blaze.materializeView/<#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1851:5
Deps.nonreactive#http://localhost:3000/packages
/deps.js?d9b2b2601bdab0f57291b38e7974a7190b8aac01:382:5
Blaze.materializeView#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:1850:3
Blaze.render#http://localhost:3000/packages
/blaze.js?309c2a3b573dca998c07c493ba4953d451b2c963:2068:3
instantiateBody#http://localhost:3000/packages
/templating.js?e2c0d3bbe4292a0b20c3083eaf4fcd0f5f91bb52:245:7
ready#http://localhost:3000/packages
/meteor.js?7a66be7a03504cd2c18dd47b699e6233b60675ed:641:6
Perhaps its because of way I built my documents, they look like that :
{
_id: "idP",
name: "nameP",
medias: [
{
_id: "idM1",
name: "nameM1"
},
{
_id: "idM2",
name: "nameM2"
},
...
]
}
I tried to give you a maximum of details about my problem, I hope someone could help me.
thanks.
EDIT :
I have fixed the behavior of my "preview column" when the user click on a media
Template.t_previewCol.current_media = function() {
var playlistId = Session.get("selected_playlist");
var mediaId = Session.get("selected_media");
var playlist = Playlists.findOne({'_id': playlistId});
for (var i = 0 ; i < playlist.medias.length ; ++i)
if (playlist.medias[i]._id._str === mediaId._str)
return(playlist.medias[i] && playlist.medias[i].name);
return (null);
};
I was trying to do
if (playlist.medias[i]._id === mediaId)
but since I've assigned the medias._id using Meteor.Collection.ObjectId() it was not working.
Now I just want to understand what is the error I pasted above.
Playlists.find returns a cursor. I think that you want Playlists.findOne.
Template.t_previewCol.current_media = function() {
var playlistId = Session.get("selected_playlist");
var mediaId = Session.get("selected_media");
var playlist = Playlists.findOne({'_id': playlistId});
return(playlist.medias[0] && playlist.medias[0].name);
};
Related
I am working on a page in a Laravel 5.7 application, that has a series of VueJS child components "Prospect" wrapped within a VueJS parent component "Prospects". The child components are table rows that each have a button to delete the row/child. The parent component has a button that deletes multiple child components.
However I am unsure as to how to get the parent component function to call the child component functions to delete the children that have been marked for deletion.
Deleting the child components one at a time from within the child component itself works fine. However I am trying to call the same Child component delete function multiple times from the loop within the parent component function deleteSelectedProspects().
From within deleteSelectedProspects() i need to access the index assigned to the child component, in order to reference the correct row in the $refs array.
How can I access the index of the child component in order to properly reference it in the $refs array inside the selectedBoxes.forEach loop?
Source code for the blade view that contains the Prospects parent component:
#extends('layouts.admin')
#section('content')
<section id="widget-grid">
<div class="row">
<article class="col-xs-12 col-sm-12 col-md-12 col-lg-12 sortable-grid ui-sortable">
<prospects inline-template class="">
<div>
<div class="jarviswidget jarviswidget-sortable" id="wid-id-1" data-widget-editbutton="false" data-widget-deletebutton="false">
<header class="ui-sortable-handle">
<div class="widget-header">
<h2><i class="fa fa-list text-orange"></i> Prospects/Leads List</h2>
</div>
<button class="btn btn-sm btn-primary d-none" #click="markSelectedProspectsContacted" id="btnMarkSelectedProspectsContacted">
<span class="fa fa-check-square-o"></span> Mark Selected as Contacted
</button>
<button class="btn btn-sm btn-danger d-none" #click="deleteSelectedProspects" id="btnDeleteSelectedProspects"><span class="fa fa-trash"></span> Delete Selected</button>
</header>
<!-- widget div-->
<div role="content">
<!-- widget content -->
<div class="widget-body">
<table id="prospectsTable" class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>
<label class="vcheck">
<input type="checkbox" id="toggleCheckAllProspects" value="1" /> <span></span>
</label>
</th>
<th>Name</th>
<th>Food Category</th>
<th>Contact</th>
<th>Admin Comments</th>
<th></th>
</tr>
</thead>
<tbody>
<tr is="prospect"
v-for="(prospect, index) in prospects"
:prospect="prospect"
:index="index"
:key="prospect.id"
ref="refIds"
#delete-prospect="removeProspect()"
#update-contacted="updateContacted(index)">
</tr>
</tbody>
</table>
</div>
<!-- end widget content -->
</div>
</div>
<!-- end jarviswidget div -->
</div>
</prospects>
</article>
</div>
</section>
#endsection
#push('js-block')
<script>
$.widget( "ui.tabs", $.ui.tabs, {
_getPanelForTab: function( tab ) {
var id = $( tab ).attr( "aria-controls" );
return $( this._sanitizeSelector( "#" + id ) );
}
});
$(function() {
$('#prospectTabs').tabs({
create: removeLoader($('#prospectTabs'))
});
$('#prospectTabsAdd').click(function() {
$('#add-tab').removeClass('d-none');
});
$('#toggleCheckAllProspects').click(function() {
$('.prospect-check').prop('checked', ($(this).is(':checked') ? true : false));
});
});
</script>
#endpush
Source code for the parent component "prospects":
<script>
export default {
data: function() {
return {
formProspect: {
inputs: {
name: '',
food_cat_id: '',
phone: '',
website: '',
contact_fname: '',
contact_lname: '',
contact_title: '',
address: '',
address2: '',
city: '',
state_id: '',
zip: '',
response_notes: ''
},
headerText: "<i class='fa fa-plus-square-o text-orange'></i> Add Prospect / Lead",
btnText: "<i class='fa fa-plus-square-o'></i> Add Prospect / Lead",
errors: false
},
formSearchProspect: {
inputs: {
keywords: '',
contacted: '',
location: '',
radius: 5
}
},
formProspectMode: 'add',
prospects: []
}
},
computed: {
},
watch: {
formProspectMode: function(newMode) {
switch(newMode) {
case 'add':
this.formProspect.headerText = "<i class='fa fa-plus-square-o text-orange'></i> Add Prospect / Lead";
this.formProspect.btnText = "<i class='fa fa-plus-square-o'></i> Add Prospect / Lead";
this.clearFormProspect();
break;
case 'edit':
this.formProspect.headerText = "<i class='fa fa-edit text-orange'></i> Edit Prospect / Lead";
this.formProspect.btnText = "<i class='fa fa-save'></i> Save Prospect / Lead";
break;
}
}
},
methods: {
fetchProspects() {
var self = this;
$.get('/prospect/fetch', function(r) {
if(r.successMsg) {
self.prospects = r.prospects;
}
})
},
searchProspects() {
var self = this;
var params = {
'keywords' : this.formSearchProspect.inputs.keywords,
'contacted' : this.formSearchProspect.inputs.contacted
};
if(this.formSearchProspect.inputs.location) {
params.location = this.formSearchProspect.inputs.location;
params.radius = this.formSearchProspect.inputs.radius;
}
$.get('/prospect/search', params, function(r) {
if(r.successMsg) {
self.prospects = r.prospects;
}
})
},
addProspect() {
var self = this;
$.ajax({
type: "POST",
dataType: "json",
async: false,
url: "/prospect/add",
data: {
name: this.formProspect.inputs.name,
food_cat_id: this.formProspect.inputs.food_cat_id,
phone: this.formProspect.inputs.phone,
website: this.formProspect.inputs.website,
contact_fname: this.formProspect.inputs.contact_fname,
contact_lname: this.formProspect.inputs.contact_lname,
contact_title: this.formProspect.inputs.contact_title,
address: this.formProspect.inputs.address,
address2: this.formProspect.inputs.address2,
city: this.formProspect.inputs.city,
state_id: this.formProspect.inputs.state_id,
zip: this.formProspect.inputs.zip,
response_notes: this.formProspect.inputs.response_notes
}, success(r) {
if(r.successMsg) {
var newProspect = self.formProspect.inputs;
newProspect.id = r.newId;
newProspect.state = r.state;
newProspect.food_cat = r.food_cat;
console.log(newProspect);
self.prospects.push(Object.assign({}, newProspect));
self.clearFormProspect();
} else if(r.errors) {
self.formProspect.errors = r.errors;
}
}
});
},
saveProspect() {
var self = this;
$.post('/prospect/edit', {
id: this.formProspect.inputs.id,
name: this.formProspect.inputs.name,
food_cat_id: this.formProspect.inputs.food_cat_id,
phone: this.formProspect.inputs.phone,
website: this.formProspect.inputs.website,
contact_fname: this.formProspect.inputs.contact_fname,
contact_lname: this.formProspect.inputs.contact_lname,
contact_title: this.formProspect.inputs.contact_title,
address: this.formProspect.inputs.address,
address2: this.formProspect.inputs.address2,
city: this.formProspect.inputs.city,
state_id: this.formProspect.inputs.state_id,
zip: this.formProspect.inputs.zip,
response_notes: this.formProspect.inputs.response_notes
}, function(r) {
if(r.successMsg) {
var savedProspect = self.prospects.filter(prospect => prospect.id == self.formProspect.inputs.id)[0];
savedProspect = Object.assign({}, self.formProspect.inputs);
self.formProspectMode = "add";
} else if(r.errors) {
self.formProspect.errors = r.errors;
}
});
},
removeProspect(index) {
this.prospects.splice(index, 1);
},
clearFormProspect() {
this.formProspect.inputs = {};
this.formProspect.errors = false;
},
deleteSelectedProspects() {
var self = this;
var selectedBoxes = self.prospects.filter(prospect => prospect.selected);
$.SmartMessageBox({
title: "<i class='fa fa-trash text-orange-dark'></i> Delete (" + selectedBoxes.length + ") Selected Prospects",
content: "Are you sure you want to delete the selected leads?",
buttons: "[No][Yes]"
}, function(e) {
if("Yes" == e) {
selectedBoxes.forEach(function(p) {
var lostChild = self.prospects.filter(prospect => prospect.id == p.id);
// HOW CAN I ACCESS THE INDEX OF THE LOST CHILD TO REFERENCE IT IN THE $refs ARRAY BELOW
// HOW CAN I ACCESS THE INDEX OF THE LOST CHILD TO REFERENCE IT IN THE $refs ARRAY BELOW
//self.$refs.refIds[lostChildIndex].deleteProspect();
});
}
});
},
markSelectedProspectsContacted() {
var self = this;
var selectedBoxes = self.prospects.filter(prospect => prospect.selected);
$.SmartMessageBox({
title: "<i class='fa fa-trash text-orange-dark'></i> Mark (" + selectedBoxes.length + ") Selected Prospects as Contacted",
content: "Are you sure you want to mark the selected leads as contacted?",
buttons: "[No][Yes]"
}, function(e) {
if("Yes" == e) {
selectedBoxes.forEach(function(p) {
/*
mark contacted
*/
});
}
});
},
updateRadiusSearchDiv() {
if($('#searchLocation').val()) {
$('#radiusSearchDiv').removeClass('d-none');
} else {
$('#radiusSearchDiv').addClass('d-none');
}
}
},
mounted() {
this.fetchProspects();
setTimeout(function() {
$('#prospectsTable').DataTable({
"columnDefs": [
{ "orderable": false, "targets": [0,4,5] }
],
"language": {
"search": "Filter Results:"
},
"dom": "ftilp"
});
$('#prospectsTable > thead > tr th:first-child').removeClass('sorting_asc');
}, 500);
$('#btnDeleteSelectedProspects, #btnMarkSelectedProspectsContacted').removeClass('d-none');
}
}
</script>
Source code for the child component "prospect":
<template>
<transition name="fade">
<tr v-if="show">
<td class="text-center align-middle">
<label class="vcheck">
<input type="checkbox" class="prospect-check" value="1" v-model="prospect.selected" /> <span></span>
</label>
</td>
<td>
<div>{{ prospect.name }}</div>
<div v-show="prospect.contacted" class="label label-success"><span class="fa fa-check-square-o"></span> Contacted!</div>
</td>
<td>{{ prospect.food_cat.title }}</td>
<td>{{ prospect.contact_fname }}<span v-if="prospect.contact_lname"> {{ prospect.contact_lname }}</span><span v-if="prospect.contact_title">, {{ prospect.contact_title }}</span></td>
<td>{{ prospect.response_notes }}</td>
<td class="text-right align-middle">
<button class="btn btn-primary" #click="updateContacted" v-show="!prospect.contacted"><span class="fa fa-check-square-o"></span> Mark As Contacted</button>
<button class="btn btn-primary" #click="editProspect"><span class="fa fa-edit"></span> Edit Lead</button>
<button class="btn btn-danger" #click="removeProspect"><span class="fa fa-trash"></span> Delete Lead</button>
</td>
</tr>
</transition>
</template>
<script>
export default {
props: ['prospect', 'index'],
data: function() {
return {
prospectData: this.prospect,
show: true,
prospectIndex: this.index
}
},
methods: {
editProspect() {
this.$parent.formProspect.inputs = this.prospectData;
this.$parent.formProspectMode = "edit";
$('#prospectTabs').tabs('option', 'active', 1);
$('#add-tab').removeClass('d-none');
window.scrollTo({ top: 0, behavior: 'smooth' });
},
updateContacted() {
var self = this;
$.SmartMessageBox({
title: "<i class='fa fa-trash text-orange-dark'></i> Mark as Contacted?",
buttons: "[No][Yes]"
}, function(e) {
if("Yes" == e) {
$.post('/prospect/updateContacted/' + self.prospectData.id, function(r) {
if(r.successMsg) {
self.$emit('update-contacted');
}
});
}
});
},
removeProspect() {
var self = this;
$.SmartMessageBox({
title: "<i class='fa fa-trash text-orange-dark'></i> Delete Prospect/Lead",
content: "Are you sure you want to delete this prospect/lead?",
buttons: "[No][Yes]"
}, function(e) {
if("Yes" == e) {
self.deleteProspect();
}
});
},
deleteProspect() {
var self = this;
$.post('/prospect/delete/' + self.prospectData.id, function(r) {
if(r.successMsg) {
self.show = false;
self.$emit('delete-prospect');
}
});
}
},
mounted() {
}
}
</script>
Your code is a little bit confused so this is just an idea that should work well, though. You should not touch refs, you just need to change the data model. Remove from the data movel and components will remove themselves.
In the parent component add a method called, say, checkProspect like
data: {
...
checkedProspects: []
},
methods: {
checkProspect(prospectId){
checkedProspects.push(prospectId);
},
deleteCheckedProspects(){
// Remove from the model by checkedProspects ids
}
}
Then add the handler:
<tr is="prospect"
v-for="(prospect, index) in prospects"
:prospect="prospect"
:index="index"
:key="prospect.id"
ref="refIds"
#checkProspect="checkProspect"
#delete-prospect="removeProspect()"
#update-contacted="updateContacted(index)">
In the child component, when you click to check it, emit the event:
this.$emit('checkProspect', this.id);
I was able to retrieve the results of my query, but I can't seem to push them to my template.
BlogApp.Views.Blog = Parse.View.extend({
template: Handlebars.compile($('#blog-tpl').html()),
className: 'blog-post',
render: function() {
var self = this,
attributes = this.model,
query = new Parse.Query(BlogApp.Models.Blog);
query.include("author");
attributes.loggedIn = Parse.User.current() != null;
if (attributes.loggedIn) {
attributes.currentUser = Parse.User.current().toJSON();
}
console.log(attributes.objectId);
var Post = Parse.Object.extend("Post");
var commentsQuery = new Parse.Query(Post);
commentsQuery.equalTo("senderParseObjectId", attributes.objectId);
commentsQuery.find({
success: function(comments) {
console.log(comments);
}
});
attributes.comment = $comments;
this.$el.html(this.template(attributes));
}
});
Template:
<script id="blog-tpl" type="text/x-handlebars-template">
{{#if comment}}
<div class="blog-sec">
<br><br>
<h2>Comments</h2><br>
<ul class="blog-comments list-unstyled">
{{#each comment}}
<li class="blog-comment">
<table>
<tr>
<td width="7.5%" style="vertical-align: middle;">
<img onError="this.src='images/ic_anonymous.png';" src="{{secureUrl author.profilePicture.url}}" class="profilePictureComments hvr-pulse-grow" id="profilePicture">
</td>
<td width="auto" style="vertical-align: middle;">
<div>#{{author.username}}</div>
<div>{{notificationText}}</div>
<div style="color: #a3a3a3;">{{time}}</div>
{{#if image.url}}
<a class="view" href="{{secureUrl image.url}}"><img class="hvr-pulse-grow profilePicture" src="{{secureUrl image.url}}" style="vertical-align: middle; width: 200px; height: 150px; border-radius: 33px;"></a>
<br> {{/if}}
</td>
</tr>
</table>
<hr>
</li>
{{/each}}
</ul>
</div>
{{/if}}
</script>
How can I render the comments that I've queried?
Router:
BlogApp.Router = Parse.Router.extend({
start: function() {
Parse.history.start({
root: '/beta/'
});
},
routes: {
'': 'index',
'post/:url': 'blog',
'admin': 'admin',
'login': 'login',
'store': 'store',
'reset': 'reset',
'logout': 'logout',
'add': 'add',
'register': 'register',
'editprofile': 'editprofile',
'changeprofilepic': 'changeprofilepic',
':username': 'userprofile'
},
blog: function(url) {
BlogApp.fn.setPageType('blog');
BlogApp.query.blog.equalTo("objectId", url).find().then(function(blog) {
var model = blog[0];
var author = model.get('author');
model = model.toJSON();
model.author = author.toJSON();
BlogApp.fn.renderView({
View: BlogApp.Views.Blog,
data: {
model: model,
comment: $comments
}
});
BlogApp.blog = blog[0];
});
},
});
I'm using <script src="js/parse-1.2.19.js"></script> in combination with http://handlebarsjs.com/ to render the data retrieved from Parse to my Views. This is a single page application.
not knowing Parse too well but it looks like you need to render the template within the success handler of your asynchronous find request - otherwise it will not have waited for this result and instead rendered the attributes data minus the results of your find request
BlogApp.Views.Blog = Parse.View.extend({
template: Handlebars.compile($('#blog-tpl').html()),
className: 'blog-post',
render: function() {
var self = this,
attributes = this.model,
query = new Parse.Query(BlogApp.Models.Blog);
query.include("author");
attributes.loggedIn = Parse.User.current() != null;
if (attributes.loggedIn) {
attributes.currentUser = Parse.User.current().toJSON();
}
console.log(attributes.objectId);
var Post = Parse.Object.extend("Post");
var commentsQuery = new Parse.Query(Post);
commentsQuery.equalTo("senderParseObjectId", attributes.objectId);
commentsQuery.find({
success: function(comments) {
console.log(comments);
attributes.comment = comments;
self.$el.html(self.template(attributes))
}
});
}
});
I'm still debating the approach but this is what I have so far. The filters work great but the data list is over 2k items, so some sort of limit is going to need to be applied. The issue is that any limit to the view would have to require a new API pull with any filter being set by any methods I've tried.
My question is should I somehow paginate from the API call or pull all data and paginate with vue? Should I perhaps filter in the backend and apply different API calls or should I use what I have below? The filtering works nicely but I am not clear how I could limit whats displayed yet filter through the entire list then return.
My Vue app and components:
Vue.component('filter', {
props: ['list', 'name'],
template: '#filter-template',
watch: {
selected: function(currentValue) {
this.$dispatch('filter-update', [this.name, currentValue]);
}
}
});
Vue.component('products', {
template: '#product-template',
created() {
this.fetchProducts();
},
data:function(){
return {
products:[],
category: 'All',
collection: 'All',
design: 'All'
}
},
events: {
'push-category': function(id) {
this.category = id;
this.filterProducts();
},
'push-collection': function(id) {
this.collection = id;
this.filterProducts();
},
'push-design': function(id) {
this.design = id;
this.filterProducts();
}
},
computed: {
total: function () {
return this.products.length;
}
},
methods: {
fetchProducts: function() {
this.$http.get('api/internal/products', function(products) {
this.$set('products', products);
});
},
filterProducts: function() {
var parent = this;
var catFilter = [];
var collFilter = [];
var designFilter = [];
// Collect all the bad guys
if(this.category == 'All') {
catFilter = [];
} else {
var key = 'Item_Disc_Group';
filter(key, this.category, catFilter);
}
if(this.collection == 'All') {
collFilter = [];
} else {
var key = 'Collection';
filter(key, this.collection, collFilter);
}
if(this.design == 'All') {
designFilter = [];
} else {
var key = 'Fancy_Color_Intensity';
filter(key, this.design, designFilter);
}
// Hide all the bad guys, show any good guys
for (var i = this.products.length - 1; i >= 0; i--) {
var product = this.products[i];
if(catFilter.indexOf(product) > -1) {
product.On_The_Web = "0";
} else if(collFilter.indexOf(product) > -1) {
product.On_The_Web = "0";
} else if(designFilter.indexOf(product) > -1) {
product.On_The_Web = "0";
} else {
product.On_The_Web = "1";
}
};
function filter(key, active, array) {
for (var i = parent.products.length - 1; i >= 0; i--) {
var product = parent.products[i];
if(product[key] != active) {
array.push(product);
}
};
}
}
}
});
new Vue({
el: '#product-filter',
created() {
this.fetchCategories();
this.fetchCollections();
this.fetchDesigns();
},
methods: {
fetchCategories: function() {
this.$http.get('api/categories', function(categories) {
this.$set('categories', categories);
});
},
fetchCollections: function() {
this.$http.get('api/collections', function(collections) {
this.$set('collections', collections);
});
},
fetchDesigns: function() {
this.$http.get('api/designs', function(designs) {
this.$set('designs', designs);
});
}
},
events: {
'filter-update': function(data) {
if(data[0] == "categories") {
this.$broadcast('push-category', data[1]);
} else if (data[0] == "collections") {
this.$broadcast('push-collection', data[1]);
} else {
this.$broadcast('push-design', data[1]);
}
}
}
});
My markup:
<div id="product-filter">
<filter :list="categories" name="categories"></filter>
<filter :list="collections" name="collections"></filter>
<filter :list="designs" name="designs"></filter>
<div class="clearfix"></div>
<products></products>
<!-- Vue Templates -->
<template id="filter-template">
<div class="form-group col-md-2">
<label>#{{ name }}</label>
<select v-model="selected" class="form-control input-small">
<option selected>All</option>
<option value="#{{ option.navision_id }}" v-for="option in list">#{{ option.name }}</option>
</select>
<span>Selected: #{{ selected }}</span>
</div>
</template>
<template id="product-template">
<div class="row mix-grid thumbnails">
<h1>#{{ total }}</h1>
<div v-for="product in products" v-if="product.On_The_Web == '1'" class="col-md-3 col-sm-6 mix category_1">
<a href="/products/#{{ product.id }}">
<div class="mix-inner">
<img class="img-responsive" src="https://s3-us-west-1.amazonaws.com/sg-retail/images/products/#{{ product.No }}.jpg" alt="">
<div class="prod-details">
<h3>#{{ product.No }}</h3>
<p></p>
<span class="price"></span>
</div>
</div>
</a>
<div class="prod-ui">
</div>
</div>
</div>
</template>
</div>
If the query takes more than a few seconds I would implement the server side filtering, but really thats preference for you. If you go with client-side filters, you can use the built in filterBy filter:
<div v-for="product in products | filterBy category in 'Item_Disc_Group' | filterBy collection in 'Collection' | filterBy design in 'Fancy_Color_Intensity'">
Just make the default value '' instead of All so that if no filter is selected then the list won't be filtered at all.
<option value="" selected>All</option>
Then you can also use a pagination filter to further page the results (borrowed from this fiddle):
data:function(){
return {
currentPage: 0,
itemsPerPage: 1,
resultCount: 0
}
},
computed: {
totalPages: function() {
return Math.ceil(this.resultCount / this.itemsPerPage)
}
},
methods: {
setPage: function(pageNumber) {
this.currentPage = pageNumber
}
},
filters: {
paginate: function(list) {
this.resultCount = list.length
if (this.currentPage >= this.totalPages) {
this.currentPage = this.totalPages - 1
}
var index = this.currentPage * this.itemsPerPage
return list.slice(index, index + this.itemsPerPage)
}
}
That would be added last after the filtering:
<div v-for="product in products | filterBy category in 'Item_Disc_Group' | filterBy collection in 'Collection' | filterBy design in 'Fancy_Color_Intensity' | paginate">
You'd also want to add buttons to support pagination, ie:
<ul>
<li v-repeat="pageNumber: totalPages">
{{ pageNumber+1 }}
</li>
</ul>
I am new to the MEAN and i am trying to make a simple CRUD application. I am getting an error of undefined on my _id and i do not understand why. This variable works withevery other function I call it in. Hopefully someone can help. I am getting the error on line 117 in my controller.js file
Here is the controller.js code for my application
todoApp.controller('TodoCtrl', function($rootScope, $scope, todosFactory) {
$scope.todos = [];
$scope.isEditable = [];
// get all Todos on Load
todosFactory.getTodos().then(function(data) {
$scope.todos = data.data;
});
// Save a Todo to the server
$scope.save = function($event) {
if ($event.which == 13 && $scope.todoInput) {
todosFactory.saveTodo({
"todo": $scope.todoInput,
"isCompleted": false
}).then(function(data) {
$scope.todos.push(data.data);
});
$scope.todoInput = '';
}
};
//update the status of the Todo
$scope.updateStatus = function($event, _id, i) {
var cbk = $event.target.checked;
var _t = $scope.todos[i];
todosFactory.updateTodo({
_id: _id,
isCompleted: cbk,
todo: _t.todo
}).then(function(data) {
if (data.data.updatedExisting) {
_t.isCompleted = cbk;
} else {
alert('Oops something went wrong!');
}
});
};
// Update the edited Todo
$scope.edit = function($event, i) {
if ($event.which == 13 && $event.target.value.trim()) {
var _t = $scope.todos[i];
todosFactory.updateTodo({
_id: _t._id,
todo: $event.target.value.trim(),
isCompleted: _t.isCompleted
}).then(function(data) {
if (data.data.updatedExisting) {
_t.todo = $event.target.value.trim();
$scope.isEditable[i] = false;
} else {
alert('Oops something went wrong!');
}
});
}
};
// Delete a Todo
$scope.delete = function(i) {
todosFactory.deleteTodo($scope.todos[i]._id).then(function(data) {
if (data.data) {
$scope.todos.splice(i, 1);
}
});
};
});
todoApp.controller('TodoCtrl', function($rootScope, $scope, todosFactory) {
$scope.todos = [];
$scope.isEditable = [];
// get all Todos on Load
todosFactory.getTodos().then(function(data) {
$scope.todos = data.data;
});
// Save a Todo to the server
$scope.save = function($event) {
if ($event.which == 13 && $scope.todoInput) {
todosFactory.saveTodo({
"todo": $scope.todoInput,
"isCompleted": false
}).then(function(data) {
$scope.todos.push(data.data);
});
$scope.todoInput = '';
}
};
//update the status of the Todo
$scope.updateStatus = function($event, _id, i) {
var cbk = $event.target.checked;
var _t = $scope.todos[i];
todosFactory.updateTodo({
_id: _id,
isCompleted: cbk,
todo: _t.todo
}).then(function(data) {
if (data.data.updatedExisting) {
_t.isCompleted = cbk;
} else {
alert('Oops something went wrong!');
}
});
};
// Update the edited Todo
$scope.edit = function($event, i) {
if ($event.which == 13 && $event.target.value.trim()) {
var _t = $scope.todos[i];
todosFactory.updateTodo({
_id: _t._id,
todo: $event.target.value.trim(),
isCompleted: _t.isCompleted
}).then(function(data) {
if (data.data.updatedExisting) {
_t.todo = $event.target.value.trim();
$scope.isEditable[i] = false;
} else {
alert('Oops something went wrong!');
}
});
}
};
// Delete a Todo
$scope.delete = function(i) {
todosFactory.deleteTodo($scope.todos[i]._id).then(function(data) {
if (data.data) {
$scope.todos.splice(i, 1);
}
});
};
});
Just is case the error is in either my factory.js code or html, I will include both.
Here is the factory.js code:
todoApp.factory('todosFactory', function($http){
var urlBase = '/api/todos';
var _todoService = {};
_todoService.getTodos = function(){
return $http.get(urlBase);
};
_todoService.saveTodo = function(todo){
return $http.post(urlBase, todo);
};
_todoService.updateTodo = function(todo) {
return $http.put(urlBase, todo);
};
_todoService.deleteTodo = function(id){
return $http.delete(urlBase + '/' + id);
};
return _todoService;
});
Here the html partial that uses the controller and factory:
<div class="container" ng-controller="TodoCtrl">
<div class="row col-md-12">
<div>
<input type="text" class="form-control input-lg" placeholder="Enter a todo" ng-keypress="save($event)" ng-model="todoInput">
</div>
</div>
<div class="row col-md-12 todos">
<div class="alert alert-info text-center" ng-hide="todos.length > 0">
<h3>Nothing Yet!</h3>
</div>
<div ng-repeat="todo in todos" class=" col-md-12 col-sm-12 col-xs-12" ng-class="todo.isCompleted ? 'strike' : ''">
<div class="col-md-1 col-sm-1 col-xs-1">
<input type="checkbox" ng-checked="todo.isCompleted" ng-click="updateStatus($event, todo._id, $index)">
</div>
<div class="col-md-8 col-sm-8 col-xs-8">
<span ng-show="!isEditable[$index]">{{todo.todo}}</span>
<input ng-show="isEditable[$index]" type="text" value="{{todo.todo}}" ng-keypress="edit($event)">
<input ng-show="isEditable[$index]" type="button" class="btn btn-warning" value="Cancel" ng-click="isEditable[$index] = false" />
</div>
<div class="col-md-3 col-sm-3 col-xs-3" >
<input type="button" class="btn btn-info" ng-disabled="todo.isCompleted" class="pull-right" value="edit" ng-click="isEditable[$index] = true" />
<input type="button" class="btn btn-danger" class="pull-right" value="Delete" ng- click="delete($index)" />
</div>
</div>
</div>
This line must be the cause of the issue:
<input ng-show="isEditable[$index]" type="text" value="{{todo.todo}}"
ng-keypress="edit($event)">
You forgot to pass the $index as the second parameter of the edit function. This should fix it:
<input ng-show="isEditable[$index]" type="text" value="{{todo.todo}}"
ng-keypress="edit($event, $index)">
I am trying to add objects to $scope.orderitems and if the object is already in the $scope.orderitems I want to change the quantity property of the object using angular.forEach instead of adding another object. Whenever I call the additem function I get an error that orderitems is not defined.
Here is my code
HTML
<input type="text" ng-model="item.name">
<button type="button" ng-click="additem(item)">
<ul>
<li ng-repeat="orderitem in orderitems">
<p>{{ orderitem.name }} | {{ orderitem.qty }}</p>
</li>
</ul>
JS
app.controller('OrderCtrl', function($scope) {
$scope.orderitems = [];
$scope.additem = function(data) {
angular.forEach(orderitems, function(orderitem) {
if (orderitem.id === data.id) {
orderitem.qty = orderitem.qty++;
} else {
data.qty = 1;
$scope.orderitems.push(data);
}
});
};
});
Here's a working fiddle: http://jsfiddle.net/rodhartzell/hbN4G/
HTML
<body ng-app="app">
<div ng-controller="OrderCtrl">
<input type="text" ng-model="item.name">
<button type="button" ng-click="additem()">add</button>
<ul>
<li ng-repeat="orderitem in orderitems">
<p>{{ orderitem.name }} | {{ orderitem.qty }}</p>
</li>
</ul>
</div>
CONTROLLER
var app = angular.module('app', []);
app.controller('OrderCtrl', function($scope) {
$scope.orderitems = [{name: 'foo', id:1, qty:1}];
$scope.additem = function() {
var exists = false;
var data = {
id : $scope.orderitems.length + 1,
name: $scope.item.name,
qty: 1
}
angular.forEach($scope.orderitems, function (item) {
if (item.name == data.name) {
exists = true;
item.qty++;
return false;
}
});
if(!exists){
$scope.orderitems.push(data);
}
};
});
$scope.orderitems = [];
$scope.additem = function(data) {
var item = null
angular.forEach(orderitems, function(orderItem) {
if (orderItem.id === data.id) {
item = orderItem
return false;
}
});
if (item == null) {
item = {
id: data.id, qty : 0
}
$scope.orderitems.push(item)
}
item.qty++
};