Toggling class names using classNameBindings in Ember.js - javascript

I'm new to emberjs and I'm having a bit of trouble understanding how classNameBindings work.
Here's my current set up - http://jsfiddle.net/inquen/AXza5/
(this is how it should work: http://jsfiddle.net/inquen/N4WJS/)
The template
<script type="text/x-handlebars">
<div class="main-container">
<div class="doors">
{{#view StandClear.DoorView class="left-door"}}<div class="door-window"></div>{{/view}}
{{#view StandClear.DoorView class="right-door"}}<div class="door-window"></div>{{/view}}
</div>
<div class="door-closure-lamp"></div>
{{#view StandClear.DoorStateView}}
Toggle Doors
{{/view}}
</div>
</script>​
StandClear = Ember.Application.create();
The JavaScript
StandClear.doorController = Ember.Controller.create({
doorsOpen: true,
toggleDoors:function(e){
this.doorsOpen = (this.doorsOpen === false) ? true : false;
console.log(this.doorsOpen);
}
});
StandClear.DoorView = Ember.View.extend({
tagName: 'div',
classNames:['car-door'],
classNameBindings: 'StandClear.doorController.doorsOpen',
})
StandClear.DoorStateView = Ember.View.extend({
tagName: 'button',
classNames:['door-controller'],
click:function(e){
StandClear.doorController.toggleDoors();
}
});
The class name binding works when the page is loaded, but doesn't change when the value it is bound to changes.
I feel there are a number of structural issues with my code as well, I'd appreciate some feedback on this as well.

Related

Aurelia - when are child compositions done?

Using the Aurelia JS framework, I need to be able to detect when the results of a repeat.for over elements have been loaded completely into the DOM by code in the parent VM. I know I can inject a listener into a #bindable and trigger the listener on attached(), but that seems very hokey to me.
Any ideas?
The problem is that you are pushing items inside the attached method and you are using dynamic composition, which usually is not a problem but your situation is a bit different. Queuing a micro-task doesn't help because it runs before your view (child.html) is loaded.
There are couple ways to solve the this problem:
1° Solution
Push your items inside bind():
bind() {
this.items.push({
view: 'child.html',
viewModel: new Child('A')
});
this.items.push({
view: 'child.html',
viewModel: new Child('B')
});
}
attached() {
let container = document.getElementById('container');
console.log('Height after adding items: ' + container.clientHeight);
this._taskQueue.queueMicroTask(() => {
console.log('Height after queued microtask: ' + container.clientHeight);
});
setTimeout(() => {
console.log('Height after waiting a second: ' + container.clientHeight);
}, 1000);
}
Fix the HTML:
<template>
<div id="container">
<div repeat.for="item of items">
<compose view-model.bind="item.viewModel" view.bind="item.view"></compose>
</div>
</div>
<div style='margin-top: 20px'>
<div repeat.for="log of logs">
${log}
</div>
</div>
</template>
Running example https://gist.run/?id=7f420ed45142908ca5713294836b2d5e
2º Solution
Do not use <compose>. Compose was made to be used when dynamic compositions are necessary. Are you sure you need to use <compose>?.
<template>
<require from="./child"></require>
<div id="container">
<div repeat.for="item of items">
<child>${item.data}</child>
</div>
</div>
</template>

Backbone/Marionette: Adding view events to a dynamically added view for a bootstrap modal

I've got a Marionette layout and for demo purposes the html looks like:
<header id="header-region" class="page-title"></header>
<section id="template-content" class="full-section">
<div id="error-messages" class="fade main-section"><!-- errors --></div>
<div id="content-region"> </div>
</section>
Its layout view's regions are:
regions: {
header: "#header-region",
content: "#content-region"
}
Up until now, I've had any given page's modal html inside the page's template html which would be contained in the content region.
I have an idea to now create a separate region for modals to be shown in.
Changing things to look like this:
Template:
<section id="template-content" class="full-section">
<div id="error-messages" class="fade main-section"><!-- errors --></div>
<div id="content-region"> </div>
<div id="modal-region"></div>
</section>
And the region:
regions: {
header: "#header-region",
content: "#content-region",
modal: "#modal-region"
}
So I'd like to be able to do something like this:
// Controller
define([], function(){
initialize: function(){},
showHeaderView: function(){
this.HeaderView = new HeaderView();
this.layout.header.show(this.HeaderView);
},
showContentView: function(){
// this.BodyView's template used to contain the modal html
this.BodyView = new BodyView();
this.layout.content.show(this.BodyView);
},
showModalView: function(){
this.ModalView = new ModalView();
this.layout.modal.show(this.ModalView);
}
});
This works and renders the modal properly but the modal's events are lost because they were originally set by this.BodyView.
The modal has a checkbox that on change runs a function that is on this.BodyView but I want to bind the events for this.ModalView from this.BodyView.
How can I accomplish that? I've tried making this.ModalView's el the same as this.BodyView's but that breaks things. I've tried to use delegateEvents as well but with no luck.
This screencast does exactly what you want: http://www.backbonerails.com/screencasts/building-dialogs-with-custom-regions
Code is here: https://github.com/brian-mann/sc01-dialogs
If you are having the HeaderView as ItemView(or CollectionView/CompositeView) in it, you can instantiate it with passing arguments like
new HeaderView({events:{
"click .x" : function(){} // your function in-line or reference
});
So same applies to ModalView.

couldn't get Bootstrap carousel to work with Ember

trying to understand why its not working.
I have something like this.
<div class="carousel slide" id="new-prospect-container">
<div class="carousel-inner">
{{#each model}}
<div class="item">
...
</div>
{{/each}}
</div>
</div>
But Botostrap's first class api means that we don't need to execute any JS methods and their widgets will work automatically. The problem is I suspect Bootstrap would have executed this prior to my {{model}} being filled up by an Ajax requests. So this Carousel won't work.
What's annoying is i already tried turning off their data-api - $(document).off('.data-api'); and manually call their carousel method - still won't work.
The carousel works with hard coded data - but once I try to populate the carousel div items from my Ember model, it just stops working.
Any idea?
Why does this exist - https://github.com/emberjs-addons/ember-bootstrap ? does it exist to exactly solve my issue here? (although there's no carousel)
1 - I hope that this jsfiddle solve your problem.
App.CarouselView = Ember.View.extend({
templateName: 'carousel',
classNames: ['carousel', 'slide'],
init: function() {
this._super.apply(this, arguments);
// disable the data api from boostrap
$(document).off('.data-api');
// at least one item must have the active class, so we set the first here, and the class will be added by class binding
var obj = this.get('content.firstObject');
Ember.set(obj, 'isActive', true);
},
previousSlide: function() {
this.$().carousel('prev');
},
nextSlide: function() {
this.$().carousel('next');
},
didInsertElement: function() {
this.$().carousel();
},
indicatorsView: Ember.CollectionView.extend({
tagName: 'ol',
classNames: ['carousel-indicators'],
contentBinding: 'parentView.content',
itemViewClass: Ember.View.extend({
click: function() {
var $elem = this.get("parentView.parentView").$();
$elem.carousel(this.get("contentIndex"));
},
template: Ember.Handlebars.compile(''),
classNameBindings: ['content.isActive:active']
})
}),
itemsView: Ember.CollectionView.extend({
classNames: ['carousel-inner'],
contentBinding: 'parentView.content',
itemViewClass: Ember.View.extend({
classNames: ['item'],
classNameBindings: ['content.isActive:active'],
template: Ember.Handlebars.compile('\
<img {{bindAttr src="view.content.image"}} alt=""/>\
<div class="carousel-caption">\
<h4>{{view.content.title}}</h4>\
<p>{{view.content.content}}</p>\
</div>')
})
})
});
2 - I don't know why the carousel isn't include in ember-boostrap.
So I have a solution for this, but it's not for the squeamish.
Bootstrap isn't specific enough about what elements it looks for in the case of the Carousel. When the carousel function goes to inventory what elements it's to manipulate, it chokes on the metamorph tags that Ember injects into the DOM. Basically, when it goes to see how many images there are, it will always find 2 more than there actually are.
I made changes to the underlying code of the carousel in the bootstrap library, here's what I did.
Line 337, change:
this.$items = this.$active.parent().children()
TO
this.$items = this.$active.parent().children('.item')
Line 379, change:
var $next = next || $active[type]()
TO
var $next = next || $active[type]('.item')
Line 401, change:
var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()])
TO
var $nextIndicator = $(that.$indicators.children('li')[that.getActiveIndex()])
This helps the carousel plugin ignore the metamorph tags.
Hope this helps.
I had the same issue and solved it by using the following method. Note that I'm using ember-cli but it's fairly easy to adapt.
This is the templates/components/photo-carousel.hbs file:
<div id="my-carousel" class="carousel slide" data-ride="carousel">
<ol class="carousel-indicators">
{{#each photo in photos}}
<li data-target="#my-carousel" data-slide-to=""></li>
{{/each}}
</ol>
<div class="carousel-inner" role="listbox">
{{#each photo in photos}}
<div class="item">
<img {{bind-attr src="photo.completeUrl" title="photo.caption" alt="photo.caption"}} />
<div class="carousel-caption">
{{photo.caption}}
</div>
</div>
{{/each}}
</div>
<!-- removed right and left controls for clarity -->
</div>
This is the components/photo-carousel.js:
export default Ember.Component.extend({
didInsertElement: function () {
// Add the active classes (required by the carousel to work)
Ember.$('.carousel-inner div.item').first().addClass('active');
Ember.$('.carousel-indicators li').first().addClass('active');
// Set the values of data-slide-to attributes
Ember.$('.carousel-indicators li').each(function (index, li) {
Ember.$(li).attr('data-slide-to', index);
});
// Start the carousel
Ember.$('.carousel').carousel();
}
});
Note that setting the active classes manually will not be required with future versions of Ember since the each helper will provide the index of the current item.

Meteor: Hide or remove element? What is the best way

I am quite new with Meteor but have really been enjoying it and this is my first reactive app that I am building.
I would like to know a way that I can remove the .main element when the user clicks or maybe a better way would be to remove the existing template (with main content) and then replace with another meteor template? Something like this would be simple and straightforward in html/js app (user clicks-> remove el from dom) but here it is not all that clear.
I am just looking to learn and for some insight on best practice.
//gallery.html
<template name="gallery">
<div class="main">First run info.... Only on first visit should user see this info.</div>
<div id="gallery">
<img src="{{selectedPhoto.url}}">
</div>
</template>
//gallery.js
firstRun = true;
Template.gallery.events({
'click .main' : function(){
$(".main").fadeOut();
firstRun = false;
}
})
if (Meteor.isClient) {
function showSelectedPhoto(photo){
var container = $('#gallery');
container.fadeOut(1000, function(){
Session.set('selectedPhoto', photo);
Template.gallery.rendered = function(){
var $gallery = $(this.lastNode);
if(!firstRun){
$(".main").css({display:"none"});
console.log("not");
}
setTimeout(function(){
$gallery.fadeIn(1000);
}, 1000)
}
});
}
Deps.autorun(function(){
selectedPhoto = Photos.findOne({active : true});
showSelectedPhoto(selectedPhoto);
});
Meteor.setInterval(function(){
selectedPhoto = Session.get('selectedPhoto');
//some selections happen here for getting photos.
Photos.update({_id: selectedPhoto._id}, { $set: { active: false } });
Photos.update({_id: newPhoto._id}, { $set: { active: true } });
}, 10000 );
}
If you want to hide or show an element conditionaly you should use the reactive behavior of Meteor: Add a condition to your template:
<template name="gallery">
{{#if isFirstRun}}
<div class="main">First run info.... Only on first visit should user see this info.</div>
{{/if}}
<div id="gallery">
<img src="{{selectedPhoto.url}}">
</div>
</template>
then add a helper to your template:
Template.gallery.isFirstRun = function(){
// because the Session variable will most probably be undefined the first time
return !Session.get("hasRun");
}
and change the action on click:
Template.gallery.events({
'click .main' : function(){
$(".main").fadeOut();
Session.set("hasRun", true);
}
})
you still get to fade out the element but then instead of hiding it or removing it and having it come back on the next render you ensure that it will never come back.
the render is triggered by changing the Sessionvariable, which is reactive.
I think using conditional templates is a better approach,
{{#if firstRun }}
<div class="main">First run info.... Only on first visit should user see this info.</div>
{{else}}
gallery ...
{{/if}}
You'll have to make firstRun a session variable, so that it'll trigger DOM updates.
Meteor is reactive. You don't need to write the logic for redrawing the DOM when the data changes. Just write the code that when X button is clicked, Y is removed from the database. That's it; you don't need to trouble yourself with any interface/DOM changes or template removal/redrawing or any of that. Whenever the data that underpins a template changes, Meteor automatically rerenders the template with the updated data. This is Meteor’s core feature.

Backbone.js backed list not being refreshed by jQuery mobile (listview(‘refresh’))

I’m trying to add sort options to a JQM list which is backed by a backbone.js collection. I’m able to sort the collection (through the collection’s view) and rerender the list, but JQM isn’t refreshing the list.
I’ve been searching and I found several questions similar to mine (problems getting the JQM listview to refresh) but I’ve been unable to get it to work.
I’ve tried calling $(‘#list’).listview(‘refresh’) and $(‘#list-page’).page() etc. to no avail. I suspect that Perhaps I’m calling the refresh method in the wrong place (to early), but I’m not sure where else I should put it (I’m just starting out with backbone).
Here’s the markup and js.
HTML:
<div data-role="page" id="Main">
<div data-role="header"><h1>Main Page</h1></div>
<div data-role="content">
<ul data-role="listview">
<li>Page 1</li>
</ul>
</div>
<div data-role="footer"><h4>Footer</h4></div>
</div>
<div data-role="page" id="Page1">
<div data-role="header">
Back
<h1>Items</h1><a href="#dvItemSort" >Sort</a></div>
<div data-role="content">
<div id="dvTest">
<ul id="ItemList" data-role="listview" data-filter="true"></ul>
</div>
</div><div data-role="footer"><h4>Footer</h4></div></div>
<div data-role="page" id="dvItemSort">
<div data-role="header"><h4>Sort</h4></div>
<a href="#Page1" type="button"
name="btnSortByID" id="btnSortByID">ID</a>
<a href="#Page1" type="button"
name="btnSortByName" id="btnSortByName">Name </a>
</div>
Javascript:
$(function () {
window.Item = Backbone.Model.extend({
ID: null,
Name: null
});
window.ItemList = Backbone.Collection.extend({
model: Item
});
window.items = new ItemList;
window.ItemView = Backbone.View.extend({
tagName: 'li',
initialize: function () {
this.model.bind('change', this.render, this);
},
render: function () {
$(this.el).html('<a>' + this.model.get('Name') + '</a>');
return this;
}
});
window.ItemListView = Backbone.View.extend({
el: $('body'),
_ItemViews: {},
events: {
"click #btnSortByID": "sortByID",
"click #btnSortByName": "sortByName"
},
initialize: function () {
items.bind('add', this.add, this);
items.bind('reset', this.render, this);
},
render: function () {
$('#ItemList').empty();
_.each(items.models, function (item, idx) {
$('#ItemList').append(this._ItemViews[item.get('ID')].render().el);
}, this);
$('#ItemList').listview('refresh'); //not working
// $('#ItemList').listview();
// $('#Page1').trigger('create');
// $('#Page1').page(); //also doesn't work
},
add: function (item) {
var view = new ItemView({ model: item });
this._ItemViews[item.get('ID')] = view;
this.$('#ItemList').append(view.render().el);
},
sortByName: function () {
items.comparator = function (item) { return item.get('Name'); };
items.sort();
},
sortByID: function () {
items.comparator = function (item) { return item.get('ID'); };
items.sort();
}
});
window.itemListView = new ItemListView;
window.AppView = Backbone.View.extend({
el: $('body'),
initialize: function () {
items.add([{ID: 1, Name: 'Foo 1'}, {ID:2, Name: 'Bar 2'}]);
},
});
window.App = new AppView;
});
EDIT: I realized that the first line of html markup I posted wasn't displaying in my post so I pushed it down a line.
EDIT 2: Here's a link to a jsfiddle of the code http://jsfiddle.net/8vtyr/2/
EDIT 3 Looking at the resulting markup, it seems like JQM adds some of the classes to the list items. I tried adding them manually using a flag to determine whether the list was being reRendered as a result of a sort and the list then displays correctly.
However, besides being somewhat of an ugly solution, more importantly my backbone events on the “item” view no longer fire (in the code example I posted I didn’t put the code for the events because I was trying to keep it as relevant as possible).
EDIT 4 I sort of got it working by clearing my cache of views and recreating them. I posted my answer below.
EDIT 5
I updated my answer with what i think is a better answer.
I'm not sure if this should be its own answer or not (i did look through the FAQ a bit), so for now I’m just updating my previous answer.
I have now found a better way to sort the list using my cached views. Essentially the trick is to sort the collection, detach the elements from the DOM and then reattach them.
So
The code now would be
$list = $('#ItemList')
$('li', $list ).detach();
var frag = document.createDocumentFragment();
var view;
_.each(item.models, function (mdl) {
view = this._ItemViews[item.get('ID')];
frag.appendChild(view.el);
},this);
$list.append(frag);
OLD ANSWER
I sort of solved the problem. I was examing the rendered elements and I noticed that when the elements were “rerendered” (after the sort) they lost the event handlers (I checked in firebug). So I decided to clear my cache of views and recreate them. This seems to do the trick, though I’m not really sure why exactly.
For the code:
Instead of:
$('#ItemList').empty();
_.each(items.models, function (item, idx) {
$('#ItemList').append(this._ItemViews[item.get('ID')].render().el);
}, this);
$('#ItemList').listview('refresh'); //not working
I clear the view cache and recreate the views.
$('#ItemList').empty();
this._ItemViews = {};
_.each(items.models, function (item, idx) {
var view = new ItemView({ model: item });
this._ItemViews[item.get('ID')] = view;
this.$('#ItemList').append(view.render().el)
}, this);
$('#ItemList').listview('refresh'); //works now
I think it would probably be better if I didn’t need to regenerate the cache, but at least this is a working solution and if I don't get a better answer then I'll just accept this one.
I had some luck in solving this, but the reason remains obscure to me.
Basically, at the top of my render view after establishing the html() of my element, I call listview(). Then, any further items I might add to a list call listview('refresh').

Categories