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);
Related
I have two tests in date-dropdown-test.js:
moduleForComponent('forms/date-dropdown', 'Integration | Component | forms/date dropdown', {
integration: true
});
test('it renders in month mode', function(assert) {
assert.expect(2);
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
this.render(hbs`{{forms/date-dropdown dateFormat='MMMM YYYY' daySpecific=false dayToUse=26 dateUnitsLong=24 startIncrement=1}}`);
// Check 24 months appear
assert.equal(this.$('option').length, 24);
// Check next month is selected by default
var today = new Date();
today.setDate(1);
today.setMonth(today.getMonth() + 1);
today.setDate(26);
var expected = moment(today).format('DD-MM-YYYY');
assert.equal(this.$('select').val(), expected);
});
test('it renders in day mode', function(assert) {
assert.expect(1);
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
this.render(hbs`{{forms/date-dropdown dateFormat='MMMM YYYY' daySpecific=true dateUnitsLong=300 startIncrement=3}}`);
// Check 300 days appear
assert.equal(this.$('option').length, 300);
});
The problem I have is when the second test runs the component from the first test is still in the DOM and found by this.$('option').length. What is the correct way to clear the DOM between or at the end of tests in Ember testing?
Or is there something more specific than this.$() to use in the context of the component rendered in the test?
EDIT
Even more confusing to me is the fact that it seems to work fine at https://github.com/yapplabs/ember-radio-button/blob/master/tests/unit/components/radio-button-test.js with multiple tests and looking at the dom, but in my second test I definitely see 324 option elements in .length instead of the 300 added by that particular component.
EDIT 2
The components code is:
export default Ember.Component.extend({
dateItems: [],
didInitAttrs: function() {
var today = new Date();
var dateItems = this.get('dateItems');
var i = 0;
if (this.get('daySpecific')) {
for (i = 0; i < this.get('dateUnitsLong'); i++) {
var nextDay = new Date();
nextDay.setDate(today.getDate() + i);
dateItems.addObject({date: nextDay, value: moment(nextDay).format('DD-MM-YYYY')});
}
}
else {
for (i = 0; i < this.get('dateUnitsLong'); i++) {
var nextMonth = new Date();
nextMonth.setDate(1);
nextMonth.setMonth(today.getMonth() + i);
nextMonth.setDate(this.get('dayToUse'));
dateItems.addObject({date: nextMonth, value: moment(nextMonth).format('DD-MM-YYYY')});
}
}
},
didInsertElement: function() {
var startDate = new Date();
if (this.get('daySpecific')) {
startDate.setDate(startDate.getDate() + this.get('startIncrement'));
}
else {
startDate.setDate(1);
startDate.setMonth(startDate.getMonth() + this.get('startIncrement'));
startDate.setDate(this.get('dayToUse'));
}
this.$('select').val(moment(startDate).format('DD-MM-YYYY')).trigger('change');
},
actions: {
dateChange: function() {
this.set('value', this.$('select').val());
}
}
});
hbs
<select class="form-control" {{action 'dateChange' on='change'}}>
{{#each dateItems as |dateItem index|}}
<option value="{{dateItem.value}}">
{{date-formatter dateItem.date dateFormat}}
</option>
{{/each}}
</select>
The idea is to create a reusable component that creates a dropdown of months or days for a given period of time and allows a default of something other than the first day/month. So looking at the first test above {{forms/date-dropdown dateFormat='MMMM YYYY' daySpecific=false dayToUse=26 dateUnitsLong=24 startIncrement=1}}would create a dropdown with 24 months from this month and default to next month.
In any case I wonder whether the final line: this.$('select').val(moment(startDate).format('DD-MM-YYYY')).trigger('change'); of didInsertElement is the offender here? Perhaps the tests continue on but this stops the teardown in the test?
The two tests pass individually if I remove one or the other.
Edit 3
Removing this.$('select').val(moment(startDate).format('DD-MM-YYYY')).trigger('change'); didnt help, perhaps its my use of didInitAttrs to create my dateItems that the #each of the template uses?
I've tried with a very simple application and integration tests seem to work here.
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
moduleForComponent('x-div', 'Integration | Component | x div', {
integration: true
});
test('it renders', function(assert) {
assert.expect(2);
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
this.render(hbs`{{x-div}}`);
assert.equal(this.$().text(), '', 'should be an empty string, got "'+this.$().text()+'"');
// Template block usage:
this.render(hbs`
{{#x-div}}
template block text
{{/x-div}}
`);
console.log(this.$('.thediv').length);
assert.equal(this.$().text().trim(), 'template block text');
});
test('it renders again', function(assert) {
assert.expect(2);
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
this.render(hbs`{{x-div}}`);
assert.equal(this.$().text(), '');
// Template block usage:
this.render(hbs`
{{#x-div}}
template block text
{{/x-div}}
`);
console.log(this.$('.thediv').length);
assert.equal(this.$().text().trim(), 'template block text');
});
this.$('.thediv').length returns 1 in both tests, teardown is done properly.
Could the component code make tests fail by any chance?
I thought this might be a bug with didInitAttrs so posted an issue on Github (http://rwjblue.jsbin.com/docepa/edit?html,js,output). It turns out that the issue was in a single line of my code:
dateItems: [],
The array is being shared on the component's prototype. As https://dockyard.com/blog/2014/04/17/ember-object-self-troll explains:
"When you don't pass any properties to create (props), all instances of the Object will share the same prototype. That's pretty much the gist of the prototypical inheritance. It means that any changes on one object will reflect on the others. That explains the behaviour in the first example."
If I had put the component on the same page twice then I would have also seen the issue outside of the test environment.
So I had two choices to resolve this, turn dataItems into a computed property or use Init. I used init like so:
dateItems: null,
init: function() {
this._super.apply(this, arguments);
this.dateItems = [];
},
dateItems are now kept nicely distinct between component instances.
I am trying to build a simple counter that will count from 0 to 100 on page load.Here is some that i came out with but does not work. I'll appreciate all the help thanks
App.AboutController = Ember.ObjectController.extend({
count: 0,
content: 0,
counterUp: function() {
var count = this.get('count') + 1;
this.set('count', count);
this.set('content', count % 100);
}
});
This seems like a great candidate for a component:
component
App.TimerCounterComponent = Ember.Component.extend({
tick: function() {
this.incrementProperty('count');
if (Ember.isEqual(this.get('count'), 100)) {
this.killCounter();
} else {
this.scheduleTick();
}
},
scheduleTick: function() {
this._counter = Ember.run.later(this, 'tick', 600);
},
setup: function() {
this.set('count', 0);
this.scheduleTick();
}.on('didInsertElement'),
killCounter: function() {
Ember.run.cancel(this._counter);
}.on('willDestroyElement')
});
component template
Count: {{count}}
then in any template you want to use the component:
{{timer-counter}}
EDIT: tidied up the jsbin code a bit
Bonus round: full js-bin example
You cannot make the counting on controller's property because it would always re-initialize and you would have always zero. You can access the application controller however and this way the property will not reset:
// better use Controller because ObjectController is getting deprecated
App.AboutController = Ember.Controller.extend({
needs: ['application'],
count: Ember.computed.alias('controllers.application.count'),
content: Ember.computed.alias('controllers.application.content'),
counterUp: (function() {
count = this.get('count') + 1 || 1;
this.set('count', count);
this.set('content', count % 100);
}).on('init')
});
If you want, you can initialize count and content properties like this:
App.ApplicationController = Ember.Controller.extend({
count: 0,
content: 0
})
If you have used the proxying behavior of ObjectController use can replace Controller with it.
As things are, your counterUp method has been declared and defined, but is never called. You have to call it in an appropriate hook. In http://emberjs.jsbin.com/wuxesosadi/1/edit?html,css,js,output you find an example that counts whenevere you click on the number.
You want to count "on page load" - a full page loade which restarts the application will always reset the counter. If you mean a transition into the route, http://emberjs.jsbin.com/wuxesosadi/4/edit?html,css,js,output would be a working example.
That said, for real world problems you would probably have a counter service or the data in a model. The controller should only hold presentation logic. And even though the controller is held in memory and re-used (as a singleton), this feels very wrong. Then again, as an example of how things work, this is perfectly valid.
Hi I am trying to do things with emberjs and google maps.
The issue with google autocomplete api is if user enters text in location search but does not select any value from dropdown suggestion in textbox the first suggestion is not selected. I found a good solution for it on stack overflow solutiion in jquery only
I am trying to do it in ember.
App.AutocompleteAddressView = Ember.TextField.extend({
tagName: 'input',
autocomplete: null,
didInsertElement: function () {
this._super();
var options = {
componentRestrictions: {
country: "xx"
}
};
this.set('autocomplete', new google.maps.places.Autocomplete(this.$()[0], options));
//var autocomplete = new google.maps.places.Autocomplete(this.$()[0], options);
},
keyDown: function (e) {
var that = this;
var suggestion_selected = $(".pac-item.pac-selected").length > 0;
if (e.keyCode === 13 && !suggestion_selected) {
//key down arrow simulations
var event = Ember.$.Event('keypress', {
keyCode: 40
});
Ember.run(this.$("#searchTextField"), 'trigger', event);
console.log('loggin autocomplete');
console.log(this.get('autocomplete'));
//adding google map event and then triggering it
google.maps.event.addListener(this.get('autocomplete'), 'place_changed', function () {
var place = that.get('autocomplete').getPlace();
that.set('autocomplete_result', place)
console.log('google event was triggered');
console.log(that.get('autocomplete_result'));
});
google.maps.event.trigger(this.get('autocomplete'), 'place_changed');
}
}
});
Now I need to do simulation part. I guess ember testing has somthing that simulates keypress but I cannot used it.
If I use the solution from the link I mentioned things work fine first time but on clicking browser back navigation it does not work.
The solution I tried in code not working
I currently have an action set up in a template for which the purpose is tracking a user's selection, and then changing pages based on that selection.
This is the applicable portion of my router:
this.resource('simpleSearch', function() {
this.resource('simpleSearchOption', {path: ':simpleSearchOption_id'});
Here's the action:
<div {{action "select" this}} class="questiontile">
And here's the controller:
App.SimpleSearchOptionController = Ember.ObjectController.extend({
needs: ["simpleSearch"],
simpleSearch: Ember.computed.alias("controllers.simpleSearch"),
actions: {
select: function(optionId) {
var nextOptionId = parseInt(this.get("id")) + 1;
var numOfOptions = this.get('simpleSearch').get('model').length;
if(nextOptionId < numOfOptions) {
console.log('going to next option');
/** What do I do here?
* This is my current implementation,
* and it works, but is it proper?
*/
this.transitionToRoute('/simpleSearch/' + nextOptionId);
}
}
}
});
The next page is basically the next index up an array of objects which is the model for the parent route/controller/view.
How I'm doing it at the moment is working - but is that proper? Is it 'Ember idiomatic'?
Sorry about previous post, accidentally deleted it!
The transitionToRoute takes two arguments, first is the resource/route name and the second is the model. So this should work
actions: {
select: function(optionId) {
var nextOptionId = parseInt(this.get("id")) + 1;
this.store.find('simpleSearch', nextOptionId).then(function(model){
this.transitionToRoute('simpleSearchOption', model);
});
//OR MAYBE YOU COULD GET IT FROM THE PARENT CONTROLLER??
/*
MAYBE
this.get('simpleSearch.content').forEach(function(model){
if(model.get('id') === nextOptionId){ do transition}
else{ alert some msg!! }
})
*/
}
}
More info here : http://emberjs.com/api/classes/Ember.Controller.html#method_transitionToRoute
Fiddle:
http://jsfiddle.net/lifeinafolder/mpcRr/
Essentially, I want to hide the current 'visible' item and make the next one 'visible' but toggleProperty doesn't seem to be working on the childView object. It just silently fails and throws no errors as well.
A CollectionView will show all items in the collection, which is not what you want. I would implement a standard view that houses the collection and displays the next button and a slide view within it that displays the selected slide when the container view sets the selected slide as content on the slide view.
A quite ugly solution, almost working. I kept the way of switching views.
The template is the same, the js looks like this:
App = Ember.Application.create();
App.slides = Ember.ArrayProxy.create({
content:[]
});
App.slidesCollectionView = Ember.CollectionView.extend({
contentBinding:"App.slides",
tagName:'div',
emptyView: Ember.View.extend({
template: Ember.Handlebars.compile("<div class=\"placeholder\">placeholder</div>")
}),
itemViewClass:"App.slideView",
classNames:["slideshow"],
click:function(){
var t = Ember.ArrayProxy.create({ content: this.get('childViews') });
var selected = t.findProperty('isVisible', true);
if(selected){
var nextSlide = t.objectAt(selected.get('contentIndex') + 1);
selected.set('isVisible', false);
if(nextSlide){
nextSlide.set('isVisible', true);
}else{
t.get('firstObject').set('isVisible', true);
}
}else{
t.get('firstObject').set('isVisible', true);
}
}
});
App.slideView = Ember.View.extend({
templateName: 'slide-item',
tagName:'div',
isVisible: false,
classNames:['slide'],
classNameBindings:['isVisible:selected']
});
setTimeout(function(){
App.slides.pushObjects([
Ember.Object.create({val:'a',index:0}),
Ember.Object.create({val:'b',index:1}),
Ember.Object.create({val:'c',index:2}),
Ember.Object.create({val:'d',index:3}),
Ember.Object.create({val:'e',index:4}),
Ember.Object.create({val:'f',index:5})
])},2000);
the fiddle:
http://jsfiddle.net/Sly7/dd6at/