Fork me on GitHub
#reagent
<
2018-09-26
>
jcb18:09:33

has anyone had to deal with a customising an audio tag/player in reagent? I'm having a bit of trouble translating standard js ways into anything clojureish

justinlee18:09:33

i haven’t dont it myself but i helped someone work through this

justinlee18:09:43

anything you do in in js is possible in cljs @jcb

jcb18:09:59

yeah I'm not that well versed in it, since I can't find any cljs examples that don't use an extern I'm trying to translate myself and it's getting a bit dicey

justinlee18:09:22

let me know what you’re trying to do and i can try to help

jcb18:09:57

thanks, each page has multiple audio files and I need to style a very generic audio player - show duration, playback position etc

jcb18:09:45

I've not dealt with extracting multiple values from eventlisteners before, mainly just simple on-clicks

jcb18:09:42

so I'm passing into a component an array of maps which contains general view data (title/url etc) then for each need to build a player

jcb18:09:07

assigning an unique id to that I can get the element

justinlee18:09:22

hang on, is the audio player all reagent code around an audio tag or is this a npm library?

jcb18:09:36

all reagent

jcb18:09:05

so really I guess the abstract question is about adding and using event listeners

jcb18:09:12

with atoms

justinlee18:09:14

ok. what does this mean: assigning an unique id to that I can get the element?

justinlee18:09:22

do you mean get the underlying dom element?

jcb18:09:40

yes, this is where it feels very js

jcb18:09:30

rather than reagent

justinlee18:09:49

first off, you should just use a ref callback and assign it to a regular atom rather than looking the element up. this is how you would do it even in javascript with plain react

justinlee18:09:07

that’s not your issue though

justinlee18:09:15

i’m not sure i understand where the event listeners come into play

jcb18:09:01

thanks that's super useful already.

jcb18:09:44

in the js examples I'm using they use event listeners to get feedback from the file of duration and current time

justinlee18:09:52

as a general matter, when you’re dealing with the dom apis that require you to make a side effect when something changes (i.e. start playback), the cleanest strategy is to separate all of that code from the presentation of the buttons and so forth. you make a component that has no dom representation and just looks to see when its props change using a form-3 component and causes the proper side effects in the dom

justinlee18:09:31

then you render the side-effecting component as a sibling to the presentation and hook it all up with some kind of atom.

justinlee18:09:02

if you past the examples, it should be straightfoward to translate

jcb18:09:53

ok, I'll have to look into this as I'm not sure I completely understand, the js example is quite long

jcb18:09:24

var music = document.getElementById('music'); // id for audio element var duration = music.duration; // Duration of audio clip, calculated here for embedding purposes var pButton = document.getElementById('pButton'); // play button var playhead = document.getElementById('playhead'); // playhead var timeline = document.getElementById('timeline'); // timeline // timeline width adjusted for playhead var timelineWidth = timeline.offsetWidth - playhead.offsetWidth; // play button event listenter pButton.addEventListener("click", play); // timeupdate event listener music.addEventListener("timeupdate", timeUpdate, false); // makes timeline clickable timeline.addEventListener("click", function(event) { moveplayhead(event); music.currentTime = duration * clickPercent(event); }, false); // returns click as decimal (.77) of the total timelineWidth function clickPercent(event) { return (event.clientX - getPosition(timeline)) / timelineWidth; } // makes playhead draggable playhead.addEventListener('mousedown', mouseDown, false); window.addEventListener('mouseup', mouseUp, false); // Boolean value so that audio position is updated only when the playhead is released var onplayhead = false; // mouseDown EventListener function mouseDown() { onplayhead = true; window.addEventListener('mousemove', moveplayhead, true); music.removeEventListener('timeupdate', timeUpdate, false); } // mouseUp EventListener // getting input from all mouse clicks function mouseUp(event) { if (onplayhead == true) { moveplayhead(event); window.removeEventListener('mousemove', moveplayhead, true); // change current time music.currentTime = duration * clickPercent(event); music.addEventListener('timeupdate', timeUpdate, false); } onplayhead = false; } // mousemove EventListener // Moves playhead as user drags function moveplayhead(event) { var newMargLeft = event.clientX - getPosition(timeline); if (newMargLeft >= 0 && newMargLeft <= timelineWidth) { playhead.style.marginLeft = newMargLeft + "px"; } if (newMargLeft < 0) { playhead.style.marginLeft = "0px"; } if (newMargLeft > timelineWidth) { playhead.style.marginLeft = timelineWidth + "px"; } } // timeUpdate // Synchronizes playhead position with current point in audio function timeUpdate() { var playPercent = timelineWidth * (music.currentTime / duration); playhead.style.marginLeft = playPercent + "px"; if (music.currentTime == duration) { pButton.className = ""; pButton.className = "play"; } } //Play and Pause function play() { // start music if (music.paused) { music.play(); // remove play, add pause pButton.className = ""; pButton.className = "pause"; } else { // pause music music.pause(); // remove pause, add play pButton.className = ""; pButton.className = "play"; } } // Gets audio file duration music.addEventListener("canplaythrough", function() { duration = music.duration; }, false); // getPosition // Returns elements left position relative to top-left of viewport function getPosition(el) { return el.getBoundingClientRect().left; }

jcb18:09:07

it's all basically the same form - add an event listener then extract a value

justinlee18:09:07

this example is a plain javascript version way of doing this. it will work, but you might have better luck importing a react library that already wraps all of this stuff up for you.

justinlee18:09:18

you can do any of this stuff in cljs if you want, though

jcb18:09:12

the react examples are very similar but use this a lot

jcb18:09:42

is that what you were referring to with the callback ref

justinlee18:09:43

this

music.addEventListener("canplaythrough", function() {
   duration = music.duration;
}, false);
turns into
(.addEventListener music "canplaythrough" (fn [] (reset! duration (.-duration music))) false)
or something like that

justinlee18:09:12

refs are a mechanism in react of getting the underlying dom element. useful for wrapping things like canvas or audio where you have to call a method on the dom element

jcb18:09:19

from the react example -

jcb18:09:21

var Timestamps = React.createClass({ render: function() { return ( <div className="Timestamps"> <div className="Time Time--current">{this.props.currentTime}</div> <div className="Time Time--total">{this.props.duration}</div> </div> )} });

jcb18:09:45

I'm not sure how to translate this

justinlee19:09:11

That example is using a very ancient style for what it is worth. It would look something like this:

(defn timestamps [current-time duration] 
  [:div.Timestamps 
     [:div {:class "Time Time--current"} (str current-time)] 
     [:div {:class "Time Time--total"} (str duration)]])

jcb19:09:30

sorry, it's only from last year!

jcb19:09:43

I guess things move quickly over there

justinlee19:09:13

yea react jumped on the es6 bandwagon quickly

justinlee19:09:36

at any rate, that’s roughly how you’d do it in reagent

jcb19:09:45

so the example uses .focus in the ref, would it be a key in the ref atom per event?

justinlee19:09:04

i’m not following your question

jcb19:09:38

sorry from the form-3 example you posted

justinlee19:09:52

the ref atom will contain the dom element. so this (some-> @!ref .focus) is just calling the focus() method on whatever dom element the top-level element of list-view renders to

jcb19:09:05

I see, so there would be a "glue" atom to store values?

justinlee19:09:07

.focus is a method on HTMLElement so no matter what list-view renders to this particular method will happen to work https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus

jcb19:09:04

sorry, just dealing with a new concept

justinlee19:09:56

the way react ref callbacks work is that once the dom element mounts, the ref callback is called with the dom element as the argument. you can do whatever you want with it, but the typical approach is to use a normal atom and store it there. then in the component-did-mount and component-did-update lifecycle methods you can do whatever dom manipulation you want.

justinlee19:09:24

you can also use it in render which sometimes is the better choice

justinlee19:09:43

if you only need it in render then you can get away with a form-2 component

jcb19:09:28

ok thanks!