I have the following code here for an embeddable component using emberjs which consists of a tab view and different tab panes. It tab-pane has a different handlebars template :
App.obj = Ember.Object.create({
"items": data['item-list']
});
var templates = [{
name: 'list',
list: '
<div class="tab-pane">
<ul class="item-list">
{{#with App.obj}}
{{#each item in items}}
<li class="c-item">
<a href="">
<div class="cc-item-a">{{item.bar}}</div>
<div class="cc-item-b">{{item.title}}</div>
<div class="cc-item-c">{{item.foo}}</div>
</a>
</li>
{{/each}}
{{/with}}
</ul>
</div>'
}, {
name: 'cal',
cal: ' <div class="tab-pane">calendar</div>'
}, {
name: 'add',
add: '<div class="tab-pane">add events</div>'
}, {
name: 'dashboard',
dashboard: '{{outlet tab}}\
<ul class="tabrow">
<li><a href="#" data-tab="list" {{action selectTab "tabA"}}>list</a></li>
<li><a href="#" data-tab="cal" {{action selectTab "tabB"}}>cal</a></li>
<li><a href="#" data-tab="add" {{action selectTab "tabC"}}>add</a></li>
</ul>'
}];
for (var i = 0; i < templates.length; i++) {
$('.container').append('<script type="text/x-handlebars" data-template-name="' + templates[i].n
ame + '">' + templates[i][templates[i].name] + "</script>");
};
The templates are appended one after another to the container. Is there a better approach to it the ember-way ? Something like -
<script type="text/x-handlebars">
{{my-component}}{{/my-component}}
</script>
The code is meant to be embeddable, so minimal no. of templates are required in the html file, that's why I wrapped it in the javascript.
Does ember has a better approach to it ?
Related
I have a two column layout. The left column has a stacked menu, and when the user selects an option the content displays in the right. What's the best way to set the active class on the selected menu link?
I have come up with a solution below, but it seems kind of verbose. Each menu item has a class assignment which is conditional upon data.activeTab. A click event handler updates the value of data.activeTab.
<template>
<div>
<div class="row">
<div class="col-md-3">
<ul class="nav nav-pills nav-stacked">
<li role="presentation" :class="{active : activeTab == 'option1'}">
<a :href="'/#/sales/' + uuid + '/option1'" #click="updateMenu">Option 1</a>
</li>
<li role="presentation" :class="{active : activeTab == 'option2'}">
<a :href="'/#/sales/' + uuid + '/option2'" #click="updateMenu">Option 2</a>
</li>
</ul>
</div>
<div class="col-md-9">
<router-view></router-view>
</div>
</div>
</div>
</template>
<script>
export default {
data : function () {
return {
activeTab : 'option1'
}
},
props : [
'uuid'
],
methods : {
updateMenu : function (event) {
var str = event.target.toString();
this.activeTab = str.substr(str.lastIndexOf('/') + 1);
}
}
}
</script>
Use router-link instead of your manual anchor tags and set the linkActiveClass in your router construction options.
I can't see your routes object, so this is a best guess based on what you have above. Refer to the router link documentation I linked above to determine the best way to set up your to attributes.
<router-link tag="li" :to="{ path: 'sales', params: { uuid : uuid, option: "option1" }}">
<a>Option 1</a>
</router-link>
<router-link tag="li" :to="{ path: 'sales', params: { uuid : uuid, option: "option2" }}>
<a>Option 2</a>
</router-link>
Then, wherever you initialized your router,
new VueRouter({
routes,
linkActiveClass: "active"
});
I'm using Intellij and Meteor to make an application and I'm trying to use Iron Router to create multiple pages, but when I call the Router in the Javascript file, it says that Router is an unresolved variable and that route is an unresolved function or method. I've checked the meteor folder and it appears that all the Iron Router files loaded fine. At the bottom of the root page I am working on it says
Oops, looks like there's no route on the client or the server for url:
"http://localhost:3000/."
If I navigate to http://localhost:3000/about, which is the only page I have a route set up for yet, the page is blank, except for my nav bar.
Here is my javascript file...
Items = new Mongo.Collection("items");
Found_items = new Mongo.Collection("found_items");
Router.route('home', {path: '/'}); // Add this route
Router.route('about', {path: '/about'});
if (Meteor.isClient) {
// This code only runs on the client
Template.body.helpers({
items: function () {
return Items.find({});
},
found_items: function () {
return Found_items.find({});
},
priceSum: function(){
var userItems = Found_items.find({
userId: this._id
}).fetch();
var prices = _.pluck(userItems, "price");
var totalTaxed = _.reduce(prices, function(sum, price){
var total = sum + parseFloat(price);
return total + (total * 0.04712);
}, 0);
return totalTaxed.toFixed(2);
},
calcTax: function () {
var userItems = Found_items.find({
userId: this._id
}).fetch();
var prices = _.pluck(userItems, "price");
var tax = _.reduce(prices, function(sum, price){
return (sum + parseFloat(price)) * 0.04712;
}, 0);
return tax.toFixed(2);
}
});
Template.body.events({
"submit .new-item": function (event) {
event.preventDefault();
var text = event.target.text.value;
Items.insert({
text: text,
createdAt: new Date(),
owner: Meteor.userId(),
username: Meteor.user().username
});
event.target.text.value = "";
}
});
Template.item.events({
"click .found": function (event, template) {
event.preventDefault();
var price = template.find('[name="price"]').value;
var text = template.find('.text').textContent;
Items.remove(this._id);
Found_items.insert({
text: text,
price: price
});
}
});
Template.body.events({
"click .remove": function(event) {
event.preventDefault();
Found_items.remove(this._id);
}
});
Accounts.ui.config({
passwordSignupFields: "USERNAME_ONLY"
});
}
And here is the HTML file
<head>
<title>Grocery List</title>
</head>
<template name="home">
<body>
<div>
<ul class="menu">
<li class="menuItem">{{> loginButtons}}</li>
<li class="menuItem">Home </li>
<li class="menuItem">About</li>
</ul>
</div>
{{#if currentUser}}
<div class="container">
<header>
<h1 id="title">Grocery List</h1>
<form class="new-item">
<input type="text" name="text" placeholder="Type to add new items" />
</form>
</header>
<ul>
{{#each items}}
{{> item}}
{{/each}}
</ul>
</div>
<div class="container">
<header>
<h1>Items Found</h1>
</header>
<ul>
{{#each found_items}}
{{> found}}
{{/each}}
</ul>
</div>
<div class="container">
<header>
<h3>
Tax: ${{calcTax}}
</h3>
<h2>
Total: ${{priceSum}}
</h2>
<button class="save">Save list</button>
</header>
</div>
{{else}}
<h3>Please log in first.</h3>
{{/if}}
</body>
</template>
<template name="item">
<li>
<button class="found">Got it!</button>
<input type="number" name="price" placeholder="Sale Price" />
<span class="text">{{text}}</span>
</li>
</template>
<template name="found">
<li>
<button class="remove">×</button>
<span class="text">{{text}}</span>
<span class="price">{{price}}</span>
</li>
</template>
<template name="about">
<head>
<title>About Grocery List</title>
</head>
<body>
<div>
<ul class="menu">
<li class="menuItem">{{> loginButtons}}</li>
<li class="menuItem">Home </li>
<li class="menuItem">About</li>
</ul>
</div>
<div class="container">
<header><h1>About</h1></header>
<p>This application was created using Meteor. It can be used to make, save and update grocery lists. Once the user is in the store, they can use it to check off items on the list, put in the price and see the total, with tax.<br>
Users can also save their previous lists to either reuse them, or compare current prices to previous ones.<br>
Future implementations of this page would also allow the user to change the tax rate depending on their location, and include coupons and other discounts in the pricing.</p>
</div>
</body>
</template>
Always add a route for the root.
Items = new Mongo.Collection("items");
Found_items = new Mongo.Collection("found_items");
Router.route('home', {path: '/'}); // Add this route
Router.route('about', {path: '/about'});
BTW, you have a head and body section within your template. That is rendered but does not have an effect in your browser.
Use the following syntax with IR's template helper pathFor:
<ul class="menu">
<li class="menuItem">{{> loginButtons}}</li>
<li class="menuItem">Home</li>
<li class="menuItem">About</li>
</ul>
In order to get your code working, I also fixed a couple of issues:
Removed head and body tags in templates.
Renamed Template.body.helpers to Template.home.helpers.
Renamed Template.body.events to Template.home.events.
Now it is adding new items to the collection and showing items.
you have to add a route with / to call localhost:3000
routing example
Router.configure({
layoutTemplate: 'layout',
});
Router.route('/', function () {
this.render('home');
},{
name: 'home'
});
Router.route('/about', function () {
this.render('about');
},{
name: 'about'
});
html
<template name="layout">
{{> yield}}
</template>
<template name="home">
<p>i am the homepage</p>
</template>
<template name="about">
<p>i am the about page</p>
</template>
I'm building a MEAN SPA and the current page I'm working on displays the users in the database. I'm pretty new to Angular so I'm still trying to wrap my head around it.
I have a parent container of which the content is controlled by an <ng-switch> and switches to show the relevant content depending on whether the user has clicked 'view all' or 'add new'. This works fine.
What I'm aiming to do now is when the user clicks on a user that's displayed in 'view-all', I want the content to switch to a view containing that users details where they can then go and edit the profile etc. What would be the best way to achieve this?
My HTML is set up like so:
Main staff view
<div class="staff" ng-controller="staffController">
<div class="side-menu">
<h2>Staff</h2>
<ul>
<li><a ng-click="tab='view-all'"><i class="fa fa-user"></i> View All</a></li>
<li><a ng-click="tab='add-new'"><i class="fa fa-plus"></i> Add New</a></li>
</ul>
</div>
<div class="page-content" ng-switch on="tab">
<div ng-switch-when="view-all" class="tab-content">
<staff-view-all></staff-view-all>
</div>
<div ng-switch-when="add-new" class="tab-content">
<staff-add-new></staff-add-new>
</div>
</div>
</div>
Directives:
.directive('staffViewAll', function () {
return {
restrict: 'E',
templateUrl: 'partials/staff/view-all.ejs'
}
})
.directive('staffAddNew', function () {
return {
restrict: 'E',
templateUrl: 'partials/staff/add-new.ejs'
}
})
view-all.ejs
<h2>View all staff</h2>
{{ users.length }} users in system
<ul>
<li ng-repeat="user in users"> <!-- click this and you see the singular view -->
<img ng-src="{{user.avatar}}?dim=100x100" />
<h3>{{user.username}}</h3>
<h4>{{user.email}}</h4>
</li>
</ul>
Use another ng-switch to switch to detailed view for the selected user.
Something like this: jsfiddle
<div ng-switch-when="list">
<ul>
<li ng-repeat="fruit in fruits">
{{fruit}}
</li>
</ul>
</div>
<div ng-switch-when="details">
<p>Details for {{ selectedFruit }}</p>
Back to list
</div>
Controller:
$scope.showDetail = function (fruit) {
$scope.selectedFruit = fruit;
$scope.moduleState = 'details';
}
$scope.showList = function()
{
$scope.moduleState = 'list';
};
I'm trying to keep track of the selected tab in the view model but I can't seem to make it work.
In the following code when you click a tab the header will update correctly but the content of the tab is not displayed. If you remove , click: $parent.selectSection then the contents are shown but the header does not update.
Now if you remove the data-bind="css: { active: selected }" from the li then it seems to work when you click the tabs but the button to select the second tab doesn't.
How can I make this work?
See: http://jsfiddle.net/5PgE2/3/
HTML:
<h3>
<span>Selected: </span>
<span data-bind="text: selectedSection().name" />
</h3>
<div class="tabbable">
<ul class="nav nav-tabs" data-bind="foreach: sections">
<li data-bind="css: { active: selected }">
<a data-bind="attr: { href: '#tab' + name }
, click: $parent.selectSection" data-toggle="tab">
<span data-bind="text: name" />
</a>
</li>
</ul>
<div class="tab-content" data-bind="foreach: sections">
<div class="tab-pane" data-bind="attr: { id: 'tab' + name }">
<span data-bind="text: 'In section: ' + name" />
</div>
</div>
</div>
<button data-bind="click: selectTwo">Select tab Two</button>
JS:
var Section = function (name) {
this.name = name;
this.selected = ko.observable(false);
}
var ViewModel = function () {
var self = this;
self.sections = ko.observableArray([new Section('One'),
new Section('Two'),
new Section('Three')]);
self.selectedSection = ko.observable(new Section(''));
self.selectSection = function (s) {
self.selectedSection().selected(false);
self.selectedSection(s);
self.selectedSection().selected(true);
}
self.selectTwo = function() { self.selectSection(self.sections()[1]); }
}
ko.applyBindings(new ViewModel());
There are several ways that you can handle this either using bootstrap's JS or by just having Knockout add/remove the active class.
To do this just with Knockout, here is one solution where the Section itself has a computed to determine if it is currently selected.
var Section = function (name, selected) {
this.name = name;
this.isSelected = ko.computed(function() {
return this === selected();
}, this);
}
var ViewModel = function () {
var self = this;
self.selectedSection = ko.observable();
self.sections = ko.observableArray([
new Section('One', self.selectedSection),
new Section('Two', self.selectedSection),
new Section('Three', self.selectedSection)
]);
//inialize to the first section
self.selectedSection(self.sections()[0]);
}
ko.applyBindings(new ViewModel());
Markup would look like:
<div class="tabbable">
<ul class="nav nav-tabs" data-bind="foreach: sections">
<li data-bind="css: { active: isSelected }">
<a href="#" data-bind="click: $parent.selectedSection">
<span data-bind="text: name" />
</a>
</li>
</ul>
<div class="tab-content" data-bind="foreach: sections">
<div class="tab-pane" data-bind="css: { active: isSelected }">
<span data-bind="text: 'In section: ' + name" />
</div>
</div>
</div>
Sample here: http://jsfiddle.net/rniemeyer/cGMTV/
There are a number of variations that you could use, but I think that this is a simple approach.
Here is a tweak where the active tab used the section name as a template: http://jsfiddle.net/rniemeyer/wbtvM/
I am quite new to backbone js and Mustache. I am trying to load the backbone collection (Object array) on page load from rails json object to save the extra call . I am having troubles rendering the backbone collection using mustache template.
My model & collection are
var Item = Backbone.Model.extend({
});
App.Collections.Items= Backbone.Collection.extend({
model: Item,
url: '/items'
});
and view
App.Views.Index = Backbone.View.extend({
el : '#itemList',
initialize: function() {
this.render();
},
render: function() {
$(this.el).html(Mustache.to_html(JST.item_template(),this.collection ));
//var test = {name:"test",price:100};
//$(this.el).html(Mustache.to_html(JST.item_template(),test ));
}
});
In the above view render, i can able to render the single model (commented test obeject), but not the collections. I am totally struck here, i have tried with both underscore templating & mustache but no luck.
And this is the Template
<li>
<div>
<div style="float: left; width: 70px">
<a href="#">
<img class="thumbnail" src="http://placehold.it/60x60" alt="">
</a>
</div>
<div style="float: right; width: 292px">
<h4> {{name}} <span class="price">Rs {{price}}</span></h4>
</div>
</div>
</li>
and my object array kind of looks like this
Finally it turns out that mustache doesn't handle array of objects sent to the template, so i had to wrap it with other object like this
render: function() {
var item_wrapper = {
items : this.collection
}
$(this.el).html(Mustache.to_html(JST.item_template(),item_wrapper ));
}
and in template just looped the items array to render the html
{{#items}}
<li>
<div>
<div style="float: left; width: 70px">
<a href="#">
<img class="thumbnail" src="http://placehold.it/60x60" alt="">
</a>
</div>
<div style="float: right; width: 292px">
<h4> {{name}} <span class="price">Rs {{price}}</span></h4>
</div>
</div>
</li>
{{/items}}
Hope it helps to someone.
Mustache can handle arrays using {{#.}}{{.}}{{/.}}
This happens because mustache expects a key/value pair -being the value an array- for Non-Empty Lists. From the mustache man page, section "Non-Empty Lists":
Template:
{{#repo}}
<b>{{name}}</b>
{{/repo}}
Hash:
{
"repo": [
{ "name": "resque" },
{ "name": "hub" },
{ "name": "rip" },
]
}
Output:
<b>resque</b>
<b>hub</b>
<b>rip</b>
If you pass only an array, mustache does not have a way to know where to expand it inside the template.
Using Hogan.js implementation.
Given template:
<ul>
{{#produce}}
<li>{{.}}</li>
{{/produce}}
</ul>
And context:
var context = { produce: [ 'Apple', 'Banana', 'Orange' ] );
We get:
<ul>
<li>Apple</li>
<li>Banana</li>
<li>Orange</li>
</ul>