I'm pretty new to using Backbone and Underscore..well Web Development in general.
I'd like to learn how to retrieve individual "model" data on-click from this template format to populate a pop-up modal. Any direction is much appreciated!
Currently, I have a list of projects that is rendered by passing my collection into this template and not using an individual view for each model item. I'm wondering how I can retrieve each project's data to populate a modal on-click.
I've tried getting the html data from e.currentTarget.html but I get undefined
<% _.each(collection, function(model){ %>
<div class="col-md-4 col-sm-6 portfolio-item">
<div class="thumbnail">
<div class="caption">
<h3><a href= <%= model.url %>><%= model.caption %></a></h3>
</div>
<img class="img-responsive" src= <%= model.image %> alt=<%= model.alt %>/>
</div>
<h3 class="project-title text-center"><%= model.title %></h3>
</div><%});%>
I had thought about following the method used in the To-Do List example by Addy Osmani, but I am trying not to have to define a View for the collection of items and a View for the individual models. I can see how this method would be able to assign a click listener for the individual models and pass that model to the modalView render, but again, trying not to do it this way (if possible).
Here is an example of the data
{
"projects": [{
"title": "Portfolio Website",
"caption": "My Showcase",
"dates": " ",
"url": "https://google.ca",
"description": "Lorem Etc Etc",
"image": "picture.jpeg",
"alt": "Portfolio Image"
}, {
"title": "Online Resume",
"caption": "Learn About Me!",
"dates": " ",
"url": "resume.com",
"description": "Look at my resume",
"image": "resume.jpeg",
"alt": "Resume Image"
}, {
"title": "Project",
"caption": "Coming Soon",
"dates": " ",
"url": "",
"description": "Lorem I Don't know what comes after Lorem",
"image": "picture.jpeg",
"alt": "Image"
}]
}
This is the Collection and View I'm using
app.projectCollection = Backbone.Collection.extend({
model: app.projectDetails,
url: '/profile.json',
parse: function(attrs){
return attrs.projects;
}
});
var projects = new app.projectCollection();
projects.fetch();
// View
app.portfolioView = Backbone.View.extend({
el: '.portfolio-body',
projectTemplate: template('portfolio-template'),
initialize: function(options){
this.listenTo(this.collection, 'add', this.render);
},
events: {
'click .portfolio-item': 'showModal' // listen for click to show modal
},
showModal: function(e){
e.preventDefault();
modalView.render(); // render the modal
// console.log("clicked" + e.currentTarget.attr('caption'));
},
render: function(){ // projects render just fine
this.$el.html(this.projectTemplate({collection: this.collection.toJSON()})); // pass in collection data for template to iterate though projects
return this;
}
});
var portfolioView = new app.portfolioView({collection: projects}); // pass in JSON
// Modal View
app.modalView = Backbone.View.extend({
className: 'modal fade',
modalTemplate: template('modal-template'),
attributes: {
tabindex: '-1',
role: 'dialog'
},
render: function(){
this.$el.html(this.modalTemplate()).modal();
return this;
}
});
var modalView = new app.modalView();
Thanks for your time and help!
As I understand, you want to have a link to your model inside template. But we can only have html there. The solution will be to have some unique id for each model.
You can add id to all your models, put it in the template like this
<div class="col-md-4 col-sm-6 portfolio-item" data-id="<%= model.id %>">
get this id in event handler
var id = $(e.currentTarget).data('id');
and get model from collection by this id
var model = this.collection.get(id);
By the way, I highly recommend you to try Marionette, it is super nice view level extension for Backbone.
Related
What is the proper way to access a nested array of JSON data to be used in Handlebars template file?
My code works in the Dev environment of chrome but doesn't work in Prod.
Here's a sample data of my JSON file.
{
"slider-card-1": [{
"card-img-1": "img/guillermo-rico-1368543-unsplash.jpg",
"card-date-1": "May 29 2019",
"card-title-1": "Deeper dive",
"card-description-1": "Deeper dive into your finances",
"btn-text-1": "Watch video",
"btn-link-1": "vid.co",
"section-header-1": "Be Heard"
},
{
"card-img-2": "img/thought-catalog-685332-unsplash.jpg",
"card-date-2": "May 29 2019",
"card-title-2": "Experience-led Agile",
"card-description-2": "Bringing experiences of our core stakeholders...",
"btn-text-2": "Learn more",
"btn-link-2": "#",
"section-header-2": "Arya"
},
{
"card-img-3": "img/thought-catalog-685332.jpg",
"card-date-3": "Febuary 4 2019",
"card-title-3": "Simplifying terminology",
"card-description-3": "It can be confusing to understand terms.",
"btn-text-3": "Read More",
"btn-link-3": "#",
"section-header-3": "Do it"
}]
}
Code for that goes into the html template file:
<script id="newsletter-template" type="text/x-handlebars-template">
<div>
<div>
<img src={{slider-card-1.[0].card-img-1}}>
</div>
<span>{{slider-card-1.[0].card-date-1}}</span>
<div>
{{slider-card-1.[0].section-header-1}}
</div>
<div>
{{slider-card-1.[0].card-title-1}}
</div>
<div>
{{slider-card-1.[0].card-description-1}}
</div>
<a href={{slider-card-1.[0].btn-link-1}}>
{{slider-card-1.[0].btn-text-1}}
</a>
</div>
</script>
Code to compile the template
<script>
var newsletter_method = {
handlerData: function (resJSON) {
var templateSource = $("#newsletter-template").html(),
template = Handlebars.compile(templateSource),
newsletterHTML = template(resJSON);
$("#my-container").html(newsletterHTML);
},
loadNewsletterData: function () {
$.ajax({
url: "./data.json",
method: 'GET',
success: this.handlerData
})
}
};
$(document).ready(function () {
newsletter_method.loadNewsletterData();
});
</script>
Now when the template is compiled, the generated HTML is blank in the browser. Is there a better way to access the nested array of JSON data in Handlebars? How do I do this?
I use html5lightbox to open images and pdf file in lightbox. Now I added angular.js and when click on a link, it takes me to enother page instead of opening it in a lightbox
var app = angular.module('MyApp', []);
var tiles =
[
{
class: "fileTile",
link: "images/9-credit-1.jpg",
header: "ToDo List",
content: "PDF File contains information about SAP sales and customers"
},
{
class: "videoTile",
link: "https://www.youtube.com/watch?v=Nfq3OC6B-CU",
header: "Fiori Tutorial",
content: "This Video contains information about SAP sales and customers"
},
{
class: "fileTile",
link: "images/canberra_hero_image_JiMVvYU.jpg",
header: "A Random Image",
content: "PDF File contains information about SAP sales and customers"
},
{
class: "fileTile",
link: "images/national-basketball-association-scoring-big-with-real-time-statistics-and-sap-hana.pdf?iframe=true",
header: "National Basketboal Team",
content: "PDF File contains information about SAP sales and customers"
}
];
app.controller("DisplayController", function($scope, $http){
$scope.tiles = tiles;
});
What can I do make html5lightbox working???
Use angular-bootstrap-lightbox.Simply awesome.
Here is the Working Plunker
html
<ul ng-controller="GalleryCtrl">
<li ng-repeat="image in images">
<a ng-click="openLightboxModal($index)">
<img ng-src="{{image.thumbUrl}}" class="img-thumbnail">
</a>
</li>
</ul>
JS (controller)
angular.module('app').controller('GalleryCtrl', function ($scope, Lightbox) {
$scope.images = [
{
'url': '1.jpg',
'caption': 'Optional caption',
'thumbUrl': 'thumb1.jpg' // used only for this example
},
{
'url': '2.gif',
'thumbUrl': 'thumb2.jpg'
},
{
'url': '3.png',
'thumbUrl': 'thumb3.png'
}
];
$scope.openLightboxModal = function (index) {
Lightbox.openModal($scope.images, index);
};
});
DEMO
Are you sure there is
data-lightbox
like identifier that means this link will be shown in lightbox..
If i remember truely you must give an attribute like this for lightbox like codes.
Or can you whare angular template/html that u are trying to use for.
Regards
I'm quite new to AngularJS.
I've given myself a small project to create a list of local businesses and then the ability to 'like' each one - with the number of likes increasing by 1 each time the 'likes' div is clicked.
Here's my HTML
<ion-content ng-controller="BusCtrl" ng-init="init()" class="has-header">
<div class="list">
<div ng-repeat="item in nodes" class="item">
<b>{{item.node.title}}</b><br>
<b>{{item.node.website}}</b><br>
<img ng-src="{{ item.node.main_image.src }}">
<div class="engagement">
<p class="likes" ng-click="plusOne($index)">{{item.node.likes}}</p>
</div>
<span ng-bind-html="item.node.summary"></span>
</div>
</div>
Here's my controller code
.controller('BusCtrl', function($scope, $http) {
$scope.init = function() {
$http.get("sample-json/business-directory.json")
.success(function(data) {
$scope.nodes = data.nodes;
$scope.plusOne = function(index) {
$scope.nodes.node[index].likes += 1;
};
$scope.browse = function(v) {
window.open(v, "_system", "location=yes");
};
window.localStorage["nodes"] = JSON.stringify(data.nodes);
})
.error(function(data) {
console.log("ERROR: " + data);
if(window.localStorage["nodes"] !== undefined) {
$scope.entries = JSON.parse(window.localStorage["nodes"]);
}
});
};
})
Here's some sample JSON
{
"nodes": [
{
"node": {
"title": "2013 Business Survey",
"website": "http://www.portumnachamber.ie",
"likes": 0,
"main_image": {
"src": "http://portumnachamber.com/sites/default/files/styles/main-business-image-teaser/public/LOVEPORTUMNA_FINAL%20LOGO_%20small_3.jpg?itok=L5IE2Du_",
"alt": "Portumna Business Survey 2013, What does Portumna Co. Galway need and want"
}
}
},
{
"node": {
"title": "All Occasions Boutique, Portumna",
"website": "http://www.portumnachamber.com",
"likes": 0,
"main_image": {
"src": "http://portumnachamber.com/sites/default/files/styles/main-business-image-teaser/public/all%20occasions%201.jpg?itok=LFvCQIAT",
"alt": "Boutique Clothes Shop Portumna county Galway Ireland, All Occasions, Portumna"
}
}
},
{
"node": {
"title": "Anthony Williams",
"website": "http://www.portumnachamber.com",
"likes": 0,
"main_image": {
"src": "http://portumnachamber.com/sites/default/files/styles/main-business-image-teaser/public/default_images/portumna-coc-logo.png?itok=V9G81lx4",
"alt": ""
}
}
},
]
}
I get a list of each business - title, website, image, and the number 0 for likes. However, when I click on the 0, I get this error:
Error: $scope.nodes.node is undefined
It seems that it's a $scope within a $scope or something like that.
Any tips? Thanks a lot.
I am going to assume that your nodes data source doesn't have another node array property on it. In this case, your function plusOne becomes:
$scope.plusOne = function(index) {
$scope.nodes[index].node.likes += 1;
};
You are iterating ( ng-repeat ) on the nodes variable from your scope, so you have to put your indexer on that property. $scope.nodes is your array, not $scope.nodes.node. For each item from $scope.nodes you have a node property.
For example $scope.nodes[0] gives you first item which has a .node property.
You are using the wrong structure.
Nodes is the array so you need to apply the index there, not to node. Node is an object which contains the parameter "likes".
The undefined item here was nodes.node[index] not the $scope
To access a node you should write $scope.nodes[index].node.likes += 1;
According to your JSON please change
$scope.nodes.node[index].likes += 1;
to
$scope.nodes[index].node.likes += 1;
Slide View
Select 1Sub Select 1Sub Select 2Sub Select 3
Select 2Sub Select 1Sub Select 2Sub Select 3
Select 3Sub Select 1Sub Select 2Sub Select 3
Please create to me a simple above output use marionette.
here below i use marionette coding
var APP = new Backbone.Marionette.Application();
APP.addRegions({
appMain:"#main"
});
APP.module("APP",function(module, app, Backbone, Marionette, $, _){
module.title = Backbone.Model.extend({});
module.titles = Backbone.Collection.extend({
model:module.title
});
module.subtitle = Backbone.Model.extend({});
module.subtitles = Backbone.Collection.extend({
model:module.subtitle
});
module.iView = Backbone.Marionette.ItemView.extend({
tagName:"li",
template:"#temp-itemview"
});
module.csView = Backbone.Marionette.CompositeView.extend({
template:"#temp-compositeview",
childView:module.iView,
itemViewContainer:"ul",
initialize:function(){
this.collection = this.model.get('subtitles');
}
});
module.collView = Backbone.Marionette.CollectionView.extend({
childView:module.csView
});
module.addInitializer(function(){
var array = [ {title:"Show 1",array:[{title:"Sub Show 1"},{title:"Sub Show 2"},{title:"Sub Show 3"}]},
{title:"Show 2",array:[{title:"Sub Show 1"},{title:"Sub Show 2"},{title:"Sub Show 3"}]},
{title:"Show 3",array:[{title:"Sub Show 1"},{title:"Sub Show 2"},{title:"Sub Show 3"}]}];
var titles = new module.titles(array);
titles.each(function(title){
var array = title.get('array');
var subtitles = new module.subtitles(array);
title.set("subtitles",subtitles);
});
var superView = new module.collView({
collection:titles
});
APP.appMain.show(superView);
});
});
APP.start();
html
<script type="text/template" id="temp-itemview">
<%= title %>
</script>
<script type="text/template" id="temp-compositeview">
<ul></ul>
</script>
Output is not clear.Please correct
The following solution is inspired from the Derick Bailey's post. The following one is adapted to backbone.marionette version 2.2.1:
<div id="tree"></div>
<script id="nodeTemplate" type="text/template">
<li><%= nodeName %></li>
</script>
app = new Backbone.Marionette.Application();
app.addRegions({
mainRegion: "#tree"
});
var TreeView = Backbone.Marionette.CompositeView.extend({
template: "#nodeTemplate",
tagName: "ul",
initialize: function(){
this.collection = this.model.nodes;
},
});
var TreeRoot = Backbone.Marionette.CollectionView.extend({
childView: TreeView
});
treeData = [
{nodeName: "Slide View"},
{nodeName: "Select 1",
nodes: [
{nodeName: "Sub Select 11"},
{nodeName: "Sub Select 12"},
{nodeName: "Sub Select 13"}
]},
{nodeName: "Select 2",
nodes: [
{nodeName: "Sub Select 21"},
{nodeName: "Sub Select 22"},
{nodeName: "Sub Select 23"}
]},
{nodeName: "Select 3",
nodes: [
{nodeName: "Sub Select 31"},
{nodeName: "Sub Select 32"},
{nodeName: "Sub Select 33"}
]},
];
TreeNode = Backbone.Model.extend({
initialize: function(){
var nodes = this.get("nodes");
if (nodes){
this.nodes = new TreeNodeCollection(nodes);
this.unset("nodes");
}
}
});
TreeNodeCollection = Backbone.Collection.extend({
model: TreeNode
});
app.addInitializer(function(options){
var treeView = new TreeRoot({
collection: options.tree
});
app.mainRegion.show(treeView);
});
$(document).ready(function(){
var tree = new TreeNodeCollection(treeData);
app.start({tree: tree});
});
Check this example, to see how it works. Please,pay attention to the external resources.
You can read Derick Bailey's post for explication and details. If you want to understand something, or if you want me to adapt this solution to yours, I mean by putting the same variables names, etc. just let me know. I'll be very pleased to help.
Hope it's useful!
You would use the Marionette Composite View for this: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.compositeview.md
You want to set the collection for each view and specify a childViewContainer (a jquery selector). If you set a collection for a composite view it will use the same view to render the children in that collection.
You can specify a childView, but I normally don't set that attribute as it should be recursively calling the same view.
I have a collection view and a model view, like so:
EventListView
|-- EventView
EventListView must display many EventViews in a one-to-many relationship. I am using the underscore _.template() function to build my views templates.
Here is my EventView template:
<h1>
<span class="date"><%= prefix %><%= dateString %></span>
<span class="title"><%= title %></span>
</h1>
<div class="caption"><%= caption %></div>
My EventView render method:
render: function () {
this.$el.html(this.template(this.model.attributes));
return this;
}
And here is my EventListView template:
<h1>
<% if(typeof(title) != "undefined") { print(title) } %>
</h1>
<%= events %>
And it's render method:
// this._EventViews is an array of eventView objects
render: function() {
var templateData = {
events: _.reduce(this._EventViews, function(memo, eventView) { return memo + eventView.$el.html(); }, "")
}
this.$el.html(this.template(templateData));
return this;
}
The problem I am having is that eventView.$el.html() contains only the HTML in my template, but I need to take advantage of the tagName, className and id attributes that Backbone views support.
Consider if I set up EventView like so:
return Backbone.View.extend({
model: EventModel
, tagName: 'article'
, className: 'event'
, template: _.template(templateText)
, render: function () {
this.$el.html(this.template(this.model.attributes));
return this;
}
});
I want to insert:
<article class="event" id="someID342">
<h1>
<span class="date">01/02/2010</span>
<span class="title"></span>
<div class="caption></div>
</h1>
</article>
but eventView.$el returns:
<h1>
<span class="date">01/02/2010</span>
<span class="title"></span>
<div class="caption></div>
</h1>
How do I insert the entire eventView element? Not just it's innerHTML.
Just reserve placeholder in your EvenListView's template
<h1><%- title %></h1>
<div class="js-events"></div>
And then render and append child views
render: function() {
this.$el.html(this.template({title: 'Title'}));
this.$events = this.$('.js-events');
_.each(this._EventViews, function (eventView) {
this.$events.append(eventView.render().$el);
}, this);
return this;
}
The render() function shouldn't be responsible for handling the setup of the view.el. This is done by Backbone in the _ensureElement function that is called when you initialize the view.
Also, the $.fn.html() function is only supposed to return the contents of the selected element.
You have many options but I think the most flexible and sustainable approach is to get each sub view to define its own template. The parent view simply appends the child elements .el property.
The advantages of this approach, your template is only compiled once. And updates to children do not require re-rendering parent and neighbouring elements.
Here is a JSBin
Example:
var ContainerView = Backbone.View.extend({
tagName: "article",
className: "event",
id: "someID342",
initialize: function(options){
//the template will now be rendered
this.childView = new ChildView()
//the rendered child will now appear within the parent view
this.el.appendChild( this.childView.el )
}
})
var ChildView = Backbone.View.extend({
tagName: "h1",
dateString:"01/02/2010",
prefix: "Date: ",
caption: "What a wonderful date!:",
title: "I am a title",
template: _.template([
'<h1>',
'<span class="date"><%= prefix %><%= dateString %></span>',
'<span class="title"><%= title %></span>',
'</h1>',
'<div class="caption"><%= caption %></div>'
].join("")),
initialize: function(){
this.render()
},
render: function(){
// because you are only altering innerHTML
// you do not need to reappend the child in the parent view
this.el.innerHTML = this.template(this)
}
})
I'd personally caution against using templates in Backbone at all. I've found that simply having a Backbone view for every component of your app becomes a lot easier to edit later. Sharing templates is a lot harder than sharing views. Of course it depends on the requirements of your project.