I am trying to make the songs in a playlist appear on screen each time a user enters a song of choice. I have the following action to insert the song that they chose into the database:
Template.search_bar.events({
'keypress #query' : function (evt,template) {
// template data, if any, is available in 'this'
if (evt.which === 13){
var url = template.find('#query').value;
$("#query").val('');
$('#playlist_container').animate({scrollTop: $('#playlist_container')[0].scrollHeight});
Template.list.search_get(url,0); //insert records into the database
}
}
});
Template.list.search_get inserts the record into the database:
Meteor.call('update_record',Template.list.my_playlist_id, song, function(err,message){});
on the server side, I am pushing records into my database with the following format:
update_record: function(sessID, songObj){
Links.update({sess: sessID}, {$push: {songs: {song_title: songObj["title"], videoId: songObj["video_id"], thumbnail: songObj["thumbnail"], index: songObj["index"]}}});
},
basically all my records have the format of:
{_id:,
sess:,
songs: [{song_title:,
videoId:,
thumbnail:,
index:},
{song_title:,
videoId:,
thumbnail:,
index:},...]
}
An array of song objects inside the songs field of the record. What I am trying to do is each time a user hits the search button, make that new song appear in the list. I am not sure how many times the render function gets called or how template renders a database object in hmtl. Currently i have the following html template for my list:
<template name="list">
<div id="playlist_container">
<ul id="playlist">
{{#each my_playlist.songs}}
{{> track}}
{{/each}}
</ul>
</div>
I believe my_playlist should call the following action on the client:
Template.list.my_playlist = function(){
console.log("myplaylist is called");
return Links.findOne({sess: Template.list.my_playlist_id});
}
It should return an object which contains an array of song object, for which i iterate through in #each my_playlist.songs, which should render each of the following track template:
<template name="track">
<li id="{{index}}" class="list_element">
<div class="destroy"> </div>
<div class="element_style">{{song_title}}</div>
</li>
</template>
However, upon successful insertion of record, i am not seeing the new song title appear. Any suggestions on how I might approach this?
This code is the problem.
Template.list.my_playlist = function(){
return Links.findOne({sess: Template.list.my_playlist_id});
}
Template.list.my_playlist_id never updates, and thus, the new template never renders.
Try this approach.
if (Meteor.isServer) {
Meteor.methods({
update_record: function(sessID, songObj){
// update Links
return Links.findOne({sess: sessID});
}
});
} else {
Meteor.call('update_record',Template.list.my_playlist_id, song, function(err, song){
Session.set('playlist', song);
});
Template.list.my_playlist = function(){
return Session.get('playlist');
}
}
Related
as I'm on my Vue spree (started recently but so far I'm really enjoying learning this framework) couple of questions rised up. One of which is how to post form from multiple components. So before I continue forward I wanted to ask you what are you thinking about this way of structuring and point me in right direction if I'm wrong.
Here it goes.
I'm working on a SPA project using ASP.NET CORE 2.1 and Vue JS Template (with webpack)(https://github.com/MarkPieszak/aspnetcore-Vue-starter) and my project is structured in several containers, something like this:
In my app-root i registered several containers
<template>
<div id="app" class="container">
<app-first-container></app-first-container>
<app-second-container></app-second-container>
<!--<app-third-container></app-third-container>-->
<app-calculate-container></app-calculate-container>
<app-result-container></app-result-container>
</div>
</template>
<script>
// imported templates
import firstContainer from './first-container'
import secondContainer from './second-container'
import calculateContainer from './calculateButton-container'
//import thirdContainer from './third-container'
import resultContainer from './result-container'
export default {
components: {
'app-first-container': firstContainer,
'app-second-container': secondContainer,
// 'app-third-container': thirdContainer,
'app-calculate-container': calculateContainer,
'app-result-container': resultContainer
}
}
</script>
In my first container I'm having several dropdowns and two input fields with my script file where I'm fetching data from API and filling dropdowns and input fields with fetched data.
Something like this ( entered some dummy code for demonstration)
<template>
<div>
<h1>Crops table</h1>
<p>This component demonstrates fetching data from the server. {{dataMessage}}</p>
<div class="form-row">
<div class="form-group col-md-6">
<label for="exampleFormControlSelect1" class="col-form-label-sm font-weight-bold">1. Some text</label>
<select class="form-control" id="exampleFormControlSelect1" v-model="pickedCropType" #change="getCropsByType()">
<option v-for="(cropType, index) in cropTypes" :key="index" :value="cropType.id" :data-imagesrc="cropType.imgPath">{{ cropType.name }}</option>
</select>
</div>
<div class="form-group col-md-6">
<label for="exampleFormControlSelect2" class="col-form-label-sm font-weight-bold">2. Some text</label>
<select class="form-control" id="exampleFormControlSelect2">
<option v-for="(crop, index) in cropSelectList" :key="index" :value="crop.id">{{ crop.name }}</option>
</select>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex'
export default {
data() {
return {
cropTypes: null,
cropSelectList: null,
crops: null,
pickedCropType: null,
}
},
methods: {
loadPage: async function () {
try {
//Get crop types and create a new array with crop types with an added imgPath property
var cropTypesFinal = [];
let responseCropTypes = await this.$http.get(`http://localhost:8006/api/someData`);
responseCropTypes.data.data.forEach(function (element) {
cropTypesFinal.push(tmpType);
});
} catch (err) {
window.alert(err)
console.log(err)
}
},
getCropsByType: async function () {
//Get crops by crop type
let responseCrops = await this.$http.get(`http://localhost:8006/api/crop/Type/${this.pickedCropType}`);
var responseCropsData = responseCrops.data.data;
this.cropSelectList = responseCropsData;
}
},
async created() {
this.loadPage()
}
}
</script>
And in my second container I have different dropdowns and different input fields with different scripts etc.
So, my questions are:
1.) I'm having required data form field in first container and in second container I'm having additional data and my submit button is separated in third container (app-result-container). So, is this proper and logical way of structuring containers if not can you point me in right direction?
2.) Is it smart to input script tag in every container where I'm processing/fetching/submitting some data for that particular container? Should I put scripts tag in separated file and keep structure clean, separating html from js file.
Example:
import { something } from 'something'
export default {
data () {
return {
someData: 'Hello'
}
},
methods: {
consoleLogData: function (event) {
Console.log(this.someData)
}
}
}
3.) Can I send input values from one container to another (In my particular case from first and second container to app-calculate-container(third container))?
How to on submit return results container with calculated imported values
If you want components to communicate or share data with one another, you will need to either emit an event from one component up to the parent and pass it down via props, or use some kind of state management model, like Vuex, where each of your components can listen to the store.
Take a look at this code sandbox: https://codesandbox.io/s/8144oy7xy2
App.vue
<template>
<div id="app">
<child-input #input="updateName" />
<child-output :value="name" />
</div>
</template>
<script>
import ChildInput from "#/components/ChildInput.vue";
import ChildOutput from "#/components/ChildOutput.vue";
export default {
name: "App",
components: {
ChildInput,
ChildOutput
},
data() {
return {
name: ""
};
},
methods: {
updateName(e) {
this.name = e.target.value;
}
}
};
</script>
ChildInput.vue
<template>
<input type="text" #input="changeHandler">
</template>
<script>
export default {
name: "ChildInput",
methods: {
changeHandler(e) {
this.$emit("input", e);
}
}
};
</script>
ChildOutput.vue
<template>
<p>{{ value }}</p>
</template>
<script>
export default {
name: "ChildOutput",
props: {
value: {
type: String,
default: ""
}
}
};
</script>
What's going on?
The ChildInput component is a text field and on every change inside it, fires an event (emits using this.$emit() and passes the whole event up).
When this fires, App is listening to the change, which fires a method that updates the name data property.
Because name is a reactive data property and is being passed down as a prop to the ChildOutput component, the screen re-renders and is updated with the text written.
Neither ChildInput nor ChildOutput knows about one another. It's the parent that listens to the event passed to it, then passes the new prop down.
This way of working is fine and simple to understand, but I would strongly recommend looking at Vuex, as this method can get messy and complicated when you go beyond trivial tasks.
i have two collections and i want to show reviews only for product thats been clicked , but i am getting all reviews (from reviews collection) no matter what product i click.
For adding and reading reviews i have following code in router.js
// Add new review
this.route('add_review', {
path:'/add_review/:_id',
template:'add_review',
data: function(){
return Products.findOne(this.params._id)
}
});
// Read reviews
this.route('reviews', {
path:'/reviews/:_id',
template:'reviews',
data: function(){
return Products.findOne(this.params._id)
}
});
});
reviews.js
Template.reviews.helpers({
'reviews': function () {
return Reviews.find( )}
})
reviews.html
<template name="reviews">
<div class="row product-row">
<div class="col-md-2">
<img class="full" src="{{image}}">
</div>
<div class="col-md-10">
<h4>{{name}}</h4>
<p>{{description}}</p>
</div>
</div>
{{#each reviews}}
<p>{{body}} </p>
{{/each}}
</template>
You can find the entire code of my project on GitHub Repository
After looking through your source code. It seems you're not saving any association between products and reviews in your database. You're going to want to store the products _id somewhere on your review object. Once that is done you will be able to filter Reviews by productId in your template. I've written some example code below.
add_review.js
Template.add_review.events({
'submit .add_review':function(event){
var rating = event.target.rating.value;
var body = event.target.body.value;
if(body!=""){
Reviews.insert({
rating:rating,
body:body,
productId:Router.current().data()._id //access the product's _id here and save it in this field
});
FlashMessages.sendSuccess('Review Added',{ autoHide: true, hideDelay: 3000 });
Router.go('/');
}
else{
alert('Review field is empty');
}
return false;
}
})
reviews.js
Template.reviews.helpers({
'reviews': function () {
return Reviews.find({productId: Router.current().data()._id}) // again, access the products _id from the router
}
})
This seems like an open-and-shut case for Template.parentData(), but to this day I've never once managed to get that bad boy working properly.
What I want is an event that updates a document depending on which button was clicked, but the buttons are themselves dependent on an array buried deeper in the document, where the _id doesn't exist.
Here's what I have:
First, a helper that sets the context peopleList:
Template.people.helpers({
peopleList: function() {
return People.find()
}
Which I use to iterate through in the HTML, printing out the first and last name of each person stored in the database, as well as their favorite colors (extraneous markup removed):
{{#each peopleList}}
<li>
{{firstName}} {{lastName}}
{{#each favoriteColors}} <button>{{this}}</button> {{/each}}
</li>
{{/each}}
It should be noted at this point that favoriteColors is a key inside the document which holds an array. So the whole thing looks something like this:
{
firstName: "Johnny",
lastName: "Boy",
favoriteColors: ["red", "blue", "blanchedAlmond"]
}
Imagine now that I want to be able to press any of these buttons, which hold the favorite colors, to set the, uh, super-duper favorite color or something. So a button click on blanchedAlmond should update the document, adding the key masterColor with the value blanchedAlmond.
The event:
'click button': function() {
var masterColor = ????
var docId = ????
Meteor.call('setMasterColor', masterColor, docId)
}
I could provide HTML data-tags that hold the color value (because this inside the event spits out some weird array with each letter separated for some reason) and even the _id with {{../_id}}, but that feels like cheating, and I really want to learn how to do the same thing inside a helper or an event.
I strongly feel like this would be a case for Template.parentData() but it returns nothing at all when I console.dir it. What should I do?
The confusion around parentData has to do with the event context. The event is attached to the template whose context is something that isn't a person or a color. Whenever you get the feeling that you need to start littering your code with data- attributes, the answer is nearly always to add more templates. For example:
html
<template name="myTemplate">
<ul>
{{#each peopleList}}
{{> person}}
{{/each}}
</ul>
</template>
<template name="person">
<li>
{{firstName}} {{lastName}}
{{#each favoriteColors}}
{{> color}}
{{/each}}
</li>
</template>
<template name="color">
<button>{{this}}</button>
</template>
js
Template.color.events({
'click button': function() {
// this context is a color - remember to convert it to a string
var masterColor = String(this);
// the parent context is a person
var docId = Template.parentData(1)._id;
return Meteor.call('setMasterColor', masterColor, docId);
}
});
My app is sort of like TelescopeJS, but a lot simpler. I'm trying to echo the particular image that has been added in the post-adding form which takes an input of the name of the post, picture, categories and description. It has 2 collections, one for Articles and the other for Images (NOT a mongo collection, it's an FS collection.) The articles collection stores the name,description and category name and the other one stores image. **My Problem is: ** in the FS collection doc, the loop
{{#each images}}
<img src="{{this.url}}" alt="" class="thumbnail" />
{{/each}}
Where images: returns Images.find({}) and my articles code is :
{{#each articles}}
<li style="margin-right: 1%;">{{>article}}</li>
{{/each}}
Where articles: returns Articles.find({})
MY articles template HAS the images loop and this causes ALL THE IMAGES in the collection to be shown in one post. I just want specific images to be shown for the specific post.
These are the events:
'change .img': function(event, template) {
FS.Utility.eachFile(event, function(file) {
Images.insert(file, function (err, fileObj) {
//Inserted new doc with ID fileObj._id, and kicked off the data upload using HTTP
});
});
},
'click .save':function(evt,tmpl){
var description = tmpl.find('.description').value;
var name = tmpl.find('.name').value;
var date=new Date();
var cat = tmpl.find('.selectCat').value;
Articles.insert({
description:description,
name:name,
time:date.toLocaleDateString()+' at '+date.toLocaleTimeString(),
author:Meteor.userId(),
userEmail:Meteor.user().username,
category:cat,
});
}
<template name="article">
{{#each images}}
<img src="{{this.url}}" alt="" class="thumbnail" />
{{/each}}
Here goes the {{name_of_post}}
Here {{the_category}}
Here {{the_description}}
</template>
So what happens is, all the images that I've uploaded so far shows in one post and all the posts' picture looks the same. Help please!
You should know that fsFile support Metadata so maybe you don't need the Articles Collection
So we can make a new eventHandler.
'click .save':function(evt,tmpl){
var description = tmpl.find('.description').value,
file = $('#uploadImagePost').get(0).files[0], //here we store the current file on the <input type="file">
name = tmpl.find('.name').value,
date=new Date(),
cat = tmpl.find('.selectCat').value,
fsFile = new FS.File(file); // we create an FS.File instance based on our file
fsFile.metadata = { //this is how we add Metadata aka Text to our files
description:description,
name:name,
time:date.toLocaleDateString()+' at '+date.toLocaleTimeString(),
author:Meteor.userId(),
userEmail:Meteor.user().username,
category:cat,
}
Images.insert(fsFile,function(err,result){
if(!err){
console.log(result) // here you should see the new fsFile instance
}
});
}
This is how our new event will look, now our .save button insert everything on the same collection.
This is how we can access to the FS.File instances fields using the keyword 'metadata.fieldName'.
For example.
Teamplate.name.helpers({
showCategory:function(){
// var category = Session.get('currentCategory') you can pass whatever data
// you want here from a select on the html or whatever.
//lets say our var its equal to 'Music'
return Images.find({'metadata.category':category});
}
})
Now we use that helper on the html like any normal collection
<template name="example">
{{#each showCategory}}
Hi my category is {{metadata.category}} <!-- we access the metadata fields like any normal field on other collection just remember to use the 'metadata'keyword -->
This is my image <img src="{{this.url}}" >
{{/each}}
</template>
I'm doing something wrong. I'm attempting to get the stored value I have in goinstant. I have a person room with a userName. The value the alert function displays is "[object Object]". Here is my code: (I left out the scripts intentionally). I provided a quick screen shot of my person data on goInstant for reference http://screencast.com/t/BtLqfrorg
<h2>Angular JS Test</h2>
<div ng-app="testapp" data-ng-controller="personCtrl">
<input type="text" ng-model="userName" />{{ userName }}
<button type="submit" id="save" name="save" >Save</button>
<script>
var testApp = angular.module('testapp', ['goangular']);
testApp.config(function($goConnectionProvider) {
$goConnectionProvider.$set('https://goinstant.net/<mykey>/test');
});
testApp.controller('personCtrl', function($scope, $goKey) {
// $goKey is available
$scope.userName = $goKey('/person/userName').$sync();
alert($scope.userName);
});
</script>
</div>
Your example would indicate that you expect $scope.userName to be a primitive value (a string). It is in fact, a model. Models provide a simple interface for updating the state of your application, and in GoAngular, that state is persisted to your GoInstant App auto-magically.
You can find more documentation on the GoAngular Model here. I thought a working example might help, so I've created a Plunker. Let's work through the script.js:
angular
.module('TestThings', ['goangular'])
.config(function($goConnectionProvider) {
$goConnectionProvider.$set('https://goinstant.net/mattcreager/DingDong');
})
.controller('TestCtrl', function($scope, $goKey) {
// Create a person model
$scope.person = $goKey('person').$sync();
// Observe the model for changes
$scope.$watchCollection('person', function(a, b) {
console.log('model is', a.$omit()); // Log current state of person
console.log('model was', b.$omit()); // Log the previous state of person
});
// After 2000 ms set the userName property of the person model
setTimeout(function() {
$scope.person.$key('userName').$set('Luke Skywalker');
}, 2000);
// Set the userName property of the person model
$scope.person.$key('userName').$set('Darth Vader');
});