I am trying to use Backbone.js with Handlebars.js to consume and display a custome JSON API.
Data is definitely being consumed and and added into the Collection.
The template renders but the table has no data in it (one completely empty row).
How would I go about debugging this?
Router
'showStatement': function() {
new app.StatementView({collection: new app.StatementCollection()});
}
Collection
app.StatementCollection = Backbone.Collection.extend({
model: app.Transaction,
url: 'http://localhost/api/public/out/123456/statement',
initialize: function() {
console.log('Init app.StatementCollection');
}
});
Model
app.Transaction = Backbone.Model.extend({
/*
defaults: {
vendor: 'Unknown',
amount: 'Unknown',
currency: 'Unknown',
date: 'Unknown'
}
*/
});
View
app.StatementView = Backbone.View.extend({
el: '#page',
template: Handlebars.getTemplate( 'account_statement' ),
initialize: function() {
console.info(this.collection);
this.render();
this.listenTo(this.collection, 'add', this.render);
this.listenTo(this.collection, 'reset', this.render);
this.collection.fetch();
},
// render library by rendering each book in its collection
render: function() {
this.$el.html( this.template( JSON.stringify(this.collection.toJSON())) ); // <------ pretty sure the problem lies here?!?
console.log('col', JSON.stringify(this.collection.toJSON()) ); // <------ the output from this is shown at the bottom
return this;
}
});
Handlebars Template
{{#if statement}}
<h1>Your Statement</h1>
<table border="1">
<thead>
<th>Date</th>
<th>Name</th>
<th>Amount</th>
</thead>
<tbody>
{{#each statement}}
{{debug}}
<tr>
<td>{{this.vendor}}</td>
<td>{{currency this.currency}}{{this.amount}}</td>
<td><time class="format-date" datetime="{{this.date}}">{{this.date}}<time></td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
<p class="warning">Sorry, nothing to show.</p>
{{/if}}
This is what my API's JSON looks like:
{"status":true,"result":[{"id":1,"vendor":"Jessops","amount":595.99,"currency":"GBP","date":"2012-11-01 04:57:04"},{"id":2,"vendor":"Starbucks","amount":6.99,"currency":"GBP","date":"2012-11-02 04:57:04"},{"id":3,"vendor":"Superdry","amount":155.99,"currency":"GBP","date":"2012-11-03 04:57:04"},{"id":6,"vendor":"Reebok Outlet","amount":205.99,"currency":"USD","date":"2012-11-05 04:57:04"}]}
Output from console.log('col', JSON.stringify(this.collection.toJSON()) );
col [{"status":true,"result":[{"id":1,"vendor":"Jessops","amount":595.99,"currency":"GBP","date":"2012-11-01 04:57:04"},{"id":2,"vendor":"Starbucks","amount":6.99,"currency":"GBP","date":"2012-11-02 04:57:04"},{"id":3,"vendor":"Superdry","amount":155.99,"currency":"GBP","date":"2012-11-03 04:57:04"},{"id":6,"vendor":"Reebok Outlet","amount":205.99,"currency":"USD","date":"2012-11-05 04:57:04"}]}]
EDIT:
I have now found that changing my render function to the following works:
render: function() {
data = this.collection.toJSON();
this.$el.html(this.template( {statement: data[0]} ));
return this;
}
This suggests that my JSON output is wrong. How can I improve my JSON to reduce the need for the [0]?
It looks like your API returns some 'meta' information (the status : true part) along with the data about the collection, and that your real data lives in the result array. I think Backbone is assuming that your data is just a single item, and it putting it into an array since it is being fed into a collection object. That's why you're needing to use data[0] to pull the first item out of that array.
I think you'd either want o modify your json so that the array currently returned in result is the top level element. Of you'd need to find a way to tell Backbone that your data lives in the result element, not at the top level.
Related
I have an element
<tbody ref="tbody">
<tr class="row" :ref="userIndex" v-for="(userData, uid, userIndex) in users" :key="uid">
in my template. I need to access/edit the DOM property like scrollTop, verticalOffset of <tr> element. How can I achieve this?
I have tried to access using this.$refs[userIndex][0].$el but its not working. I can see all the properties in the console but I am unable to access them. However this.$refs.tbody.scrollTop works.
Below is the snap showing console.log(this.$refs)
console.log(this.$refs[userIndex])
console.log(this.$refs[userIndex][0])
As you can see when I use this.$refs[userIndex][0] I don't see the DOM properties
A $ref object will only have a $el property if it is a Vue component. If you add a ref attribute to a regular element, the $ref will a reference to that DOM Element.
Simply reference this.$refs[userIndex][0] instead of this.$refs[userIndex][0].$el.
To see the properties of that element in the console, you'll need to use console.dir instead of console.log. See this post.
But, you can access properties of the element like you would any other object. So, you could log the scrollTop, for instance, via console.log(this.$refs[userIndex][0].scrollTop).
I don't think verticalOffset exists. offsetTop does. To console log an Dom element and its property, use console.dir
Open the browser console and run this working snippet:
var app = new Vue({
el: '#app',
data: {
users: {
first: {
name: "Louise"
},
second: {
name: "Michelle"
}
}
},
mounted() {
console.dir(this.$refs[1][0])
console.log('property: ', this.$refs[1][0].offsetTop)
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<table><tbody ref="tbody">
<tr :ref="userIndex" v-for="(userData, uid, userIndex) in users" :key="uid">
<td>{{userData}}: {{userIndex}}</td>
</tr>
</tbody>
</table>
</div>
I want to display the data in a tabular fashion, Data will be fetched using the joins of collection from Mongo DB , I have some experience in Datatables that I have used in my previous projects
I have been trying with lots of Meteor stuff to accomplish this.
What I tried and What is the result:
I am using loftsteinn:datatables-bootstrap3 (https://github.com/oskarszoon/meteor-datatables-bootstrap3/) I am trying to display the data using joining of two collections, for Joining of collections I am using : https://atmospherejs.com/reywood/publish-composite.
The Issue : as the data gets fetched and the page gets rendered with the table it shows 0 records, but after a few seconds rows get populated and datatable gets filled but still shows 0 records.
To Counter this issue I have to set timeout for few seconds and then it shows correctly.
Is there any better way, as I feel that in case the data gets increased, I may face issues again.
Possible Solutions with Other Packages?
Is anybody has expirience in Joining of Collections and displaying correctly in the tabular format with Pagination, Sorting and Search?
I would Appriciate any help in this.
CODE:
TEMPLATE
<template name="testdatatable">
{{#if Template.subscriptionsReady}}
<table cellpadding="0" cellspacing="0" border="0" class="table table-striped table-bordered" id="myTable">
<thead>
<tr>
<th>Todos Name</th>
<th>List Name</th>
</tr>
</thead>
<tbody>
{{#each todos}}
<tr>
<td>{{name}}</td>
<td>{{lists.name}}</td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
<div class="loading">{{>spinner}}</div>
{{/if}}
</template>
TEMPLATE HELPER
Template.testdatatable.helpers({
todos: function() {
console.log(Todos.find());
return Todos.find();
},
lists: function() {
return Lists.findOne(this.listId);
}
});
PUBLISH COMPOSITE using ( reywood:publish-composite )
Meteor.publishComposite('tabular_Todos', {
find: function () {
return Todos.find();
},
children: [
{
find: function(todos) {
return Lists.find({_id: todos.listId });
}
}
]
});
ROUTING USING (iron Router)
Router.route('/testdatatable', {
name: 'testdatatable',
template: 'testdatatable',
onAfterAction: function(){
calltestdatatable();
},
subscriptions: function(){
return Meteor.subscribe('tabular_Todos');
}
});
OTHER FUNCTIONS
ON RENDERED
Template.testdatatable.onRendered(function(){
setTimeout(calldatatable, 2000);
});
SETTING A TIMEOUT TO DELAY THE DATATABLE
function calltestdatatable(){
setTimeout(calldatatable, 2000);
}
DATATABLE INITIALISATION
function calldatatable(){
$('#myTable').DataTable();
}
DATABASE
todos Collection
lists Collection
Thanks and Best Regards,
Manu
here is my route that fixes the problem :
Router.route('testdatatable', {
path: '/testdatatable',
name: 'testdatatable',
template: 'testdatatable',
waitOn: function() {
return [
Meteor.subscribe('tabular_Todos')
];
},
subscriptions: function(){
return Meteor.subscribe('tabular_Todos');
}
});
and template
Template.testdatatable.onRendered(function(){
$('#myTable').dataTable();
});
(as I used the --example todos, I had to change {{name}} as {{text}} to display the todo text)
Search, pagination, sorting works fine with meteor add lc3t35:datatables-bootstrap3 !
Answer to your first question.
your data needs to come into minimongo first, then client side will be able to render those data. As a workaround you can use loading animation. a quick solution would be using sacha:spin package. and your Blaze code will be something similar to this.
{{#if Template.subscriptionsReady}}
// load your view
{{else}}
<div class="loading">{{>spinner}}</div>
{{/if}}
your second problem is that ,db gets filled but table shows nothing except row skeleton. It's most probably because you have either problems with helper function or in the Blaze view. As you've not posted any code, it's hard to identify problem.
And to other questions: there are quite good numbers of packages for pagination and search. checkout atmosphere. you'll find some popular packages like pages and easy-search. you need to decide which suits for your project.
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 trying to pass a custom form attribute (category) through jQuery UI Autocomplete to use in a product search. The form looks like <form id="BulkOrderForm" category="samplecategory"><input></input>...</form> and contains inputs that use the autocomplete script. There can be several forms on each page, so I need to be able to get the category value from the form that contains the active input field.
Here's my source:
function autocomplete() {
$("input.wcbulkorderproduct").autocomplete({
element: function(){
var element = $('form#BulkOrderForm').attr('category');
return element;
},
source: function(request, response, element){
$.ajax({
url: WCBulkOrder.url+'?callback=?&action='+acs_action+'&_wpnonce='+WCBulkOrder.search_products_nonce,
dataType: "json",
data: {
term: request.term,
category: element
},
success: function(data) {
response(data);
}
});
}
});
}
Any thoughts on how this can be acheived?
If I'm understanding correctly, you're trying to use the active input's parent form in the ajax request. Here's a way to achieve that:
Html:
<form data-category="form1">
<input type="text" />
<input type="text" />
</form>
<form data-category="formB">
<input type="text" />
<input type="text" />
</form>
JS:
$('form').each(function () {
var category = $(this).data('category');
$(this).find('input').autocomplete({
source: function (request, response) {
response([category]);
}
});
});
Instead of using autocomplete on a catch-all selector that gets inputs from all forms, first select the forms themselves. For each one, extract the category, then select all child inputs and call autocomplete on that result. Then you can use the category variable in the ajax call - in the example I'm simply passing it to the callback to display.
http://jsfiddle.net/Fw2QA/
I'll give you another solution, you can lookup the parent form of the active input, and extract the attribute from it. Because I don't know if this category in your form is dynamic or no, or either if you can control all of the process involved in your code, I'll give you a more generic solution, although if that attribute is dynamic "Turch" solution is way better than mine, by letting the data functionality of jquery handle the attribute changes, if it's static, than you can just do it like this:
function autocomplete() {
var element = $("input.wcbulkorderproduct").autocomplete({
source: function(request, response){
$.ajax({
url: WCBulkOrder.url+'?callback=?&action='+acs_action+'&_wpnonce='+WCBulkOrder.search_products_nonce,
dataType: "json",
data: {
term: request.term,
category: element
},
success: function(data) {
response(data);
}
});
}
}).parents('form').first().attr('category');
//chained call, sets autocomplete, grabs the parent form and the attribute
//which is saved on the variable element, and is used on every call through
//javascript context inheritance.
}
UPDATE
A little example illustrating my suggestion (provided by #Turch > thanks), can be found here.
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');
}
}