Something like a public function in a component


#1

Hi,

I’ve a component “player-video” like that (there are some other things in it, that why it’s a component) :

video-player.hbs <video src="{{src}}" ></video>

and I want some buttons in his parent’s component to make the video play from one time to an other, for example one button : 1.05 to 1.32 minute, an other button : from 0.35 to 0.59 … etc

in the player-video component there will certainly be a function something like that : playExtract(from, to){ this.$('video').currentTime = from setInterval(function(){ $this.$('video').currentTime > to && video.pause() ...

playing an extract of a video is not exactly a “property” of the player-video component, and component in Ember doesn’t have public functions.

what is the nice “Ember way” to achieve this sort of thing ? does it need to use a computed properties (ex : “isPlaying”) inside the player-video component that fire the playExtract function when the property “isPlaying” value change… ? , something more Ember like ?


#2

The Ember way is called “Data down, actions up”. You change the data being passed in to the component and write computed properties (or hook into didReceiveAttrs) to change the state and behavior of your component. If you need to pass data back up to the parent omponent, you use actions.


#3

Thank you for your answer.

I already know the Ember principle “Data down, actions up” and think I understand it quite well. It keep component well decoupled from the rest of the appli.

What is confuse for me is how with this principle component can still have some sort of “functionality”. In “old school” OOP, objects have function like person.run(), person.talk()… Component doesn’t permit that, so does functionalities of components have to be attributes ? : isRunning = true/false speed=n isTalking=true/false whatToSay=“hello world” … etc

In my case of videoplayer, should I make component’s attributes something like : isPlayingExtract=true/false from=n to=n, and inside the component have private function that make it playing the extract when properties change ?

Sorry my bad English, hope this understandable.


#4

A component doesn’t expose methods. It takes data (attributes) and callbacks (actions) and outputs DOM. While it may be possible to call a method from outside a component in Ember, this is considered bad practice. A component should be seen more like a function than an object.


#5

may be my question is not clear and with some code it will be more understandable :

I’ve a video-player component : video-player.hbs <video src="film.mp4"></video> <button {{action 'playExtract' 'john_drink_a_beer'}} >John drink a bear</button> <button {{action 'playExtract' 'jeanne_read_a_book'}} >Jeanne read a book</button>

video-player.js import Ember from ‘ember’;

export default Ember.Component.extend({
	
	extracts: {'john_drink_a_beer':[20.5,27.45], 'jeanne_read_a_book':[31.2,38]},
	
	actions: {
	    playExtract(pExtract) {
	    	let video = this.$("video")[0];
	    	let from = this.get('extracts')[pExtract][0];
	    	let to = this.get('extracts')[pExtract][1];

	    	video.currentTime = from;
	    	video.play();

	    	let inter = setInterval(function(){
	    		if(video.currentTime > to){
	    			video.pause();
	    			clearInterval(inter);
	    		}
	    	},100)
	    }
	  },
});

In this example the buttons are inside the component, what need to be done if the buttons were outside the component ? like that for ex : index.hbs {{#player-video}}{{/player-video}} <button>John drink a bear</button> <button>Jeanne read a book</button>


#6

I created an ember-twiddle that demonstrates the expected way to handle interaction between components. The example creates two simple components that mimic your desired behavior and demonstrates passing actions between them.

If you aren’t familiar with ember-twiddle it is an online tool for quickly creating and playing with ember apps to work out ideas or problems.

Basically one component hands another a reference to one of its actions by assigning it through its template.

//some-component has an action called doWork
//other-component receives a reference to that action that it can 
//call internally by the name doStuff

{{#some-component}}
    {{other-component doStuff=(action 'doWork')}}
{{/some-component}}

This is referred to as a closure action. Take a look at the ember guides section on actions for more details on how to handle interaction between components.


#7

Hi,

Thank you very much to have taken time to answer and make this example on ember-twiddle !

In your example the buttons are “childs” of the video-player component, so it’s look more like my first example. What I want is the buttons to be “brothers” of the video component.

I’ve made a test on twiddle, and I did try with a “observer” on a property “extractToPlay” of the video-player component. It work but : 1/ We can’t restart an extract by pressing a second time on the same button, as the property “extractToPlay” don’t change and the observer don’t fire. 2/ This solution seems to me “twisted” and I’m very not sure that it’s the ember-way to do that sort of thing ?


#8

Generally, you should avoid observers for many reasons. They are not in line with the data down, actions up paradigm, and also, they have many issues such as being synchronous and called at a time when it is not a good idea to update DOM or other properties. If you need more details on why you should avoid them, see https://www.youtube.com/watch?v=vvZEddrClAQ


#9

It’s Kafkaesque.

Thank again ! but your responses are all the time “you should not…” "the ember principles are…" I know all these things and I’m not looking for a quirk solution, I just miss a little thing of how components should communicates in some particulars cases :sweat:

My “observer test” was just to explain what I’m looking for.

Anybody can tell me the simple way to do that ? I thought that should be a very trivial use of Ember.


#10

https://ember-twiddle.com/7accff55b503501abfd466f2a5d85715

I’m not sure if I have made it better or worse :grin:

You can yield a function into a block component which will allow you to have a public function like you originally desired. However, the downside is that you will have to make the buttons children of the video player… but it’s pretty close to your example.

Let me know if this helps. This was the very first thing to popped into my head.

Cheers, Benjamin


#11

Hi, and thank you for your attention !

"I’m not sure if I have made it better or worse :grin:" Your solution make the buttons/player-video working exactly like I except, but now it seems that the problem is that components are not any more well decoupled and these solution could rapidly become spagetti :spaghetti: code, but tell me if you think not ?.

Suddenly I realise that because the “<video>” html tag is already an “encapsulated” component with its own functionalities/properties, it make it hard to decouple in an MV way. Normally, in the “view” layer (component) it should be only “datas” comming from the “model” via an attribute : one frame of the video (= array of pixels), and the logic should be in a “model” layer : play, pause, currentTime… Unfortunately we can’t separate parts (view/model) of the “<video >” tag.

Nevertheless, may I should try to put the logic (playExtract method, interval, currentFrame) In a separated “model” class, and just activate the play/pause method of the player-video component / video tag via attributes ? Give a try…

Otherwise I found http://www.thesoftwaresimpleton.com/blog/2015/04/26/inter-component/ and http://www.thesoftwaresimpleton.com/blog/2015/04/27/event-bus/, that may be good approach ?, give a try too… (but fell that “bus” could rapidly become :spaghetti: too ?)