I have a component which renders a selection of images, and I have some CSS for a "hover mask" which then covers the image, displaying some information about that image when a user hovers over it.
The dimensions of the hover mask need to match those of the image when the app first loads, and when the window resizes.
Here's the basic mark up
<div class="call-out-item">
<img src="......">
<div class="hover-mask">Some info about this image</div>
</div>
Here's the CSS:
.call-out-item {
position:relative;
display:none;
}
.call-out-item .hover-mask {
position:absolute;
top:0;
left:0;
}
.call-out-item:hover .hover-mask {
opacity: .95;
background: rgba(33,153,232,0.8);
cursor:pointer;
}
Here's my component code (not sure about the correct use of Ember.run.next (!)
// other component code here
const {$} = Ember;
init() {
this._super(...arguments);
this.handleResize = function() {
let itemWidth = $(".call-out-item img").width();
let itemHeight = parseInt(itemWidth/1.5);
$(".hover-mask").css({"width":itemWidth,"height":itemHeight});
}.bind(this);
Ember.run.next(this, this.handleResize);
$(window).on('resize', Ember.run.bind(this, this.handleResize));
},
// etc etc
I am getting variable results with this code in Chrome and Firefox so must be doing something wrong here!
Make sure you unbind the event when the component is destroyed, otherwise your event will continue to trigger while the user is on other routes
initResizeObserver: on("init", function () {
$(window).on('resize', this.handleResize.bind(this));
}),
willDestroyElement () {
this._super(...arguments);
$(window).off("resize");
},
handleResize (e) {
console.log(e)
},
Here is my solution. One of the problems (in Firefox) was that the above code was not calculating the width of the image initially, so I am now calculating the width (and height) of the parent container and setting the dimensions of the image to 100% for width and height.
I still think there are some glitches when I resize the window (does anyone know why?) but I hope this helps!
// component code
import Ember from 'ember';
const {$} = Ember;
export default Ember.Component.extend({
tagName:'div',
classNames:[],
init() {
this._super(...arguments);
this.handleResize = function() {
let itemWidth = parseInt($(".call-out-item").width());
let itemHeight = parseInt(itemWidth/1.5);
$(".hover-mask").css({"width":itemWidth,"height":itemHeight});
};
Ember.run.next(this, this.handleResize);
$(window).on('resize', Ember.run.bind(this, this.handleResize));
},
// rest of code
Related
I am building a 'Watch this deal' functionality, which is similar to FB 'like' feature. (Ember version 1.13)
Here is the scenario:
There is an icon beside every deal which will enable the current user to 'watch' or 'not watch' the deal. The actions are completed and working and changes on the UI is also working fine. The problem is, when I click on that icon, I become a watcher of the deal but the icon doesn't change. I have to refresh the page to see that change.
controller:
actions:{
// add and remove watchers
addToWatcher: function(deal) {
var _this = this;
var currentUser = this.get('currentUser');
deal.get('watchers').addObject(currentUser);
deal.save().then(function () {
Ember.get(_this, 'flashMessages').success("You are now watching");
}, function() {
// Ember.get(_this, 'flashMessages').danger('apiFailure');
});
},
removeWatcher: function(deal) {
var _this = this;
var currentUser = this.get('currentUser');
deal.get('watchers').removeObject(currentUser);
deal.save().then(function () {
Ember.get(_this, 'flashMessages').success("You are now watching");
}, function() {
// Ember.get(_this, 'flashMessages').danger('apiFailure');
});
}
}
templates:
{{#if (check-watcher deal currentUser.id)}}
<i class="fa fa-2x sc-icon-watch watched" {{action 'removeWatcher' deal}} style="padding: 5px 10px;"></i><br>
{{else}}
<i class="fa fa-2x sc-icon-watch" {{action 'addToWatcher' deal}} style="padding: 5px 10px;"></i><br>
{{/if}}
Here check-watcher is a helper I wrote to check if the deal is being watched by the current user. If it is, the icon will be Red and clicking on it again will trigger 'removeWatcher' action. If not, icon will be black and clicking on it will make user watch the deal.
check-watcher helper:
import Ember from 'ember';
export function checkWatcher(object, currentUser) {
var currentUser = object[1];
var watchers = object[0].get('watchers').getEach('id');
if (watchers.contains(currentUser)) {
return true;
} else{
return false;
}
}
export default Ember.Helper.helper(checkWatcher);
If I were to just change the class, that would have been easy, but I have to change the action too in the views, that's where it's a little tricky.
So, how to make the change in UI happen between adding and removing watchers without refreshing the page?
In short, you need to define a compute method for the helper:
import Ember from 'ember';
export function checkWatcher(object, currentUser) {
var currentUser = object[1];
var watchers = object[0].get('watchers').getEach('id');
if (watchers.contains(currentUser)) {
return true;
} else{
return false;
}
}
export default Ember.Helper.extend({ compute: checkWatcher });
In that case, the helper will recompute its output every time the input changes.
And there is not need to change an action in a template. You could always call 'toggleWatcher' action from template, and then decide what to do in the controller:
toggleWatcher(deal) {
var currentUser = this.get('currentUser');
if (deal.get('watchers').contains(currentUser)) {
this.send('removeWatcher', deal);
} else {
this.send('addToWatcher', deal);
}
}
Context: http://emberjs.jsbin.com/diyirecu/23/edit
Is there anyway to have the Ember.beforeOberver callback run first and then the property changes? Or should I be using a different strategy all together?
Not sure if you can turn that event synchronous... However, I refactored your code to use some simple jQuery patterns (callbacks... I know, very non-Ember of me) that achieve the same thing. The code is clunky compared to what you were looking to achieve, but it works.
Example JSBin: http://emberjs.jsbin.com/diyirecu/26/edit
I also changed popObject to removeObject so that it actually removed the item you clicked on.
App.IndexController = Ember.ObjectController.extend({
content: {},
colors: ['red','blue','green'],
color: 'orange',
actions: {
addColor: function () {
var color = this.get('color'),
$ul = Ember.$('ul');
if (color) {
$ul.fadeOut(500, function () {
this.get('colors').pushObject(color);
this.set('color', '');
$ul.fadeIn();
}.bind(this));
}
},
removeColor: function (color) {
var $ul = Ember.$('ul');
$ul.fadeOut(500, function () {
this.get('colors').removeObject(color); //Changed from popObject
$ul.fadeIn();
}.bind(this));
}
},
});
Whats a good way or a pattern to creating modal forms in EmberJS. Something like this.
I will describe the way i manage modal views with a CSS approach:
Add CSS class names:
.modal {
-webkit-transition: -webkit-transform #modal-duration #ease-animation-style;
-webkit-transform: translate3d(0,0,0);
-webkit-backface-visibility: hidden;
}
.modal.from-left.is-hidden {
-webkit-transform: translate3d(-320px,0,0);
}
.modal.from-right.is-hidden {
-webkit-transform: translate3d(320px,0,0);
}
.modal.from-up.is-hidden {
-webkit-transform: translate3d(0,-1*#app-height,0);
}
.modal.from-down.is-hidden {
-webkit-transform: translate3d(0,#app-height,0);
}
Add custom events to your Application Namespace to receive the transitionEnd event in your view:
Yn.Application = Em.Application.extend( Em.Evented, {
customEvents: {
webkitTransitionEnd: 'transitionEnd',
......
}
});
Now create a mixin to be used in your view as:
Yn.Modal = Em.Mixin.create({
isHidden: true,
classNameBindings: ['isHidden'],
classNames: ['modal'],
transitionEnd: function(event) {
// transitionEnd triggers multiple times
// http://stackoverflow.com/questions/4062105/webkit-transitionend-event-grouping
if ( event.originalEvent.propertyName === '-webkit-transform' &&
$(event.target)[0] === this.$()[0] ) {
var eventName = this.get('isHidden') ? 'transitionHide' : 'transitionShow' ;
this.trigger(eventName);
}
}
});
You can now insert the view in the DOM via appendTo or any handlebars view template or whatever method you use, and manages your view with its isHidden property which can be bound for example to a controller UI property, you could also interact with the view with the view lifecycle hooks as didInsertElement or the new defined as transitionHide, transitionShow hooks.
You can use my modified Bootstrap.ModalPane
ex:
App.ContactView = Bootstrap.ModalPane.extend({
closeOnEscape: false,
showBackdrop: true,
showCloseButton: false,
heading: 'Contact',
primary: "Save changes",
secondary: "Cancel",
classNames: ["large-modal"],
bodyViewClass: Ember.View.extend({
templateName: 'contact-body'
}),
callback: function (opts, event) {
var controller = this.get('controller');
if (opts.primary) {
controller.save();
} else {
controller.cancel();
}
}
});
In you controller you can then do something like this:
editContact: function (contact) {
var contactController = this.controllerFor('contact');
contactController.set('content', contact);
var contactView = App.ContactView.create({
controller: contactController
});
contactView.append();
},
You can also define your own modalPaneTemplate with customizations. It's the principle of how the Boostrap.ModelPane works that matters, default it only supports 2 buttons at the bottom. If you want 5 buttons, or buttons in the header, start coding yourself and create a custom modalPaneTemplate.
I am working on an application with HTML5 video where I have to display the current time of the video tag of my page in a seperate label.
I tried to define a property in my view which returns the formatted time of the video, something like
videoCounter: function() {
return App.seconds2String(Math.floor(App.getCurrentVideoTime() + 0.5));
}.property().volatile()
which returns the current time of the video as mm:ss.
The function App.getCurrentVideoTime() gets the current time from the video element in the page (so it is not an Ember property and therefore an not be bound) like this:
getCurrentVideoTime: function() {
var videoElement = document.getElementById('video');
if (videoElement) {
return Math.round(videoElement.currentTime * 10) / 10;
}
else {
return 0;
}
}
The handlebars view contains
<label id="videoCounter">{{view.videoCounter}}</label>
Unfortunately the value is not updated as the video plays, i. e. it displays 00:00 all the time.
How can i trigger an update for the computed property that is only dependent on the current time of the video tag?
I don't need high accuracy, the timer should only show the current time of the video in seconds.
Any ideas?
Thank you so much!
Thomas
It's not obvious from the code you've stated whether you are using a custom view, but I would create a custom Video view and bind to the timeupdate event, see http://jsfiddle.net/pangratz666/fzNMb/:
Handlebars:
<script type="text/x-handlebars" >
{{view App.Video width="400px" controllerBinding="App.videoController"}}
{{App.videoController.currentTimeFormatted}}
</script>
JavaScript:
App.Video = Ember.View.extend({
srcBinding: 'controller.src',
controls: true,
tagName: 'video',
attributeBindings: 'src controls width height'.w(),
didInsertElement: function() {
this.$().on("timeupdate", {
element: this
}, this.timeupdateDidChange);
},
timeupdateDidChange: function(evt) {
var video = evt.data.element;
var currentTime = evt.target.currentTime;
video.setPath('controller.currentTime', currentTime);
}
});
Based on what I said and the use of setInterval mentioned by #Zack
Jsfiddle: http://jsfiddle.net/Sly7/xgqkL/
App = Ember.Application.create({
// computed property based on currentTime change
videoCounter: function(){
return this.get('currentTime');
}.property('currentTime'),
currentTime: 0
});
// increment currentTime property every second
setInterval(function(){
App.incrementProperty('currentTime');
}, 1000);
I have an app which lists albums. When album is clicked on both AlbumView and App.overlay (also a view) are displayed.
App.overlay = Ember.View.create({...}) (Lightbox-like overlay).
and:
App.AlbumView = Ember.View.extend({
// close the selected album view by closing the overlay
close: function() {
App.overlay.close();
}
});
And here's the problem: I want to be able to close those both views by clicking on the overlay, but I want overlay to remain independent of AlbumView, so that I can use the overlay in other places (i.e. without introducing a coupling between the two). How can I do it?
Here is my current implementation, with tight coupling, which I really don't like:
App.overlay = Ember.View.create({
// handle clicking anywhere on the overlay
click: function() {
this.close();
},
// close the overlay (setting selectedAlbum's controller content to null hides the AlbumView)
close: function() {
App.selectedAlbumController.set('content', null); // this should not be here
this.remove();
}
});
I'm only just learning ember, so take this with a grain of salt...
You could add a 'visible' property to the overlay, and then observe it from the other AlbumView. Like this:
var overlay = Ember.View.create({
visible: true,
click: function() {
this.close();
},
close: function() {
this.set('visible', false);
this.remove();
}
});
App.AlbumView = Ember.View.extend({
overlayClosed: function() {
App.selectedAlbumController.set('content', null);
this.remove();
}.observes('overlay.visible')
});
What about extracting your close method in a mixin?
App.AlbumClosing = Ember.Mixin.create({
close: function() {
App.selectedAlbumController.set('content', null);
this.remove();
}
});
var overlay = Ember.View.create(App.AlbumClosing, {
click: function() {
this.close();
}
});