If you Command (Mac) or Control (PC) click the Fork button, it will open the newly forked Pen in a new tab in your browser. That’s new behavior. Before, it would open the fork in the same tab, no matter how you click. That was unfortunate, as Cassie called out:
Why didn’t it work like this before? Well, that’s what Shaw and I get into in this podcast. It’s a smidge complicated. The root of it is that that Fork button isn’t a hyperlink. It’s a button handled by JavaScript because of the nature of how it works (a fork might have data that only the client knows about: unsaved code changes). But Shaw found a way to make it work anyway, by essentially passing the metaKey
information through all the forking process until that moment we had an opportunity to open that new tab.
Time Jumps
- 00:32 What was the request?
- 03:37 Being careful with target="_blank"
- 05:14 The whole forking process
- 07:16 A form for example
- 08:41 How forks work on a pen
- 10:47 How did you pass the data?
- 13:41 It’s behaving like a link
- 15:29 Sponsor: Notion
- 17:18 A few issues
- 20:14 People forking instead of saving
Sponsor: Notion
Notion is an incredible organizational tool. Individuals can get a ton out of it, but I find the most benefit in making it a home base for teams. It can replace so many separate tools (documents, meeting notes, todos, kanbans, calendars, etc) that it really becomes the hub of doing work, and everything stays far more organized than disparate tools ever could.
Transcript
[Radio channel adjustment]
Announcer: Today, on CodePen Radio.
Chris Coyier: Hey, everybody. Welcome to another CodePen Radio. This is 361, we're calling Forking Problems. We're going to talk about the little, tiniest, babyest feature ever that was requested and then shipped by us just recently that Shaw worked on. What was the request? I mean the most recent one. I'm sure we've gotten it literally 100 times.
Stephen Shaw: Yeah, we've probably heard this one before, but I just got tagged in a tweet by Cassie Evans over at GSAP, which she does a lot of community work and uses CodePen a lot and forks other people's Pens and does a lot of things to help other people using GSAP.
The fork feature is very important for her, and she tweeted a petition for allowing the fork button to open in a new tab. You know, a really simple request that makes a lot of sense.
Chris: Right.
Stephen: When I'm browsing around, I'll click on links, either right click or hold down my Cmnd - or whatever - to open them in a new tab. So, you know it makes a lot of sense for that.
Chris: It does. Surely, as far as customers that you want to listen to, Cassie is pretty high on the list. Not just because she's awesome, which she is -- high-five, Cassie -- but like you said, her particular job is very fork heavy and, for a developer evangelist, it's something -- it's just an example of, if you're running a business, there are certain people's opinions that you extra listen to when they ask you for something, especially when they do it as politely and all that.
Stephen: Oh, right. Yeah. The politeness is key. But it's also indicative if this power user is requesting this feature. It's indicative that the community, as a whole, probably has some kind of need there for that.
Chris: Yeah.
Stephen: In her tweet, she mentioned that she is uncertain when she's actually forked it or if it's still working on the original.
Chris: Yeah.
Stephen: What's the status of it? That's a big problem there, so it's bigger than just "Open this fork in a new tab."
Chris: It is. Can't you relate? I'd be like--
Stephen: Oh, yeah.
Chris: There'll be times when I'm intentionally trying to make variations of a Pen, and I'll be like, "Oh, I'm so smart. I'm going to remember to hit "fork" now, so I'm not messing up the previous variation. Then I'll hit it, but then I'm like, "But did I, though?"
Stephen: Right. The only thing that changes is the actual URL. There's really not much of an indication that things have been forked and you're not going to mess something up.
That's kind of the central problem here is, "Did I actually fork it? Am I working on the original, or am I working on a new one? Am I going to lose work?"
Chris: Exactly. I could see from the outside where you'd think, "Okay. Well, yes. It should open in a new tab," so maybe the user can just command click it then (or control click it in PC)?
Maybe. It's just there's one particular problem with that in that that button, you can't. You can't. You can't because what I mean by command click and just open in a new tab is that works for links.
Stephen: Right.
Chris: It has forever. I'm a big fan of that, actually.
Stephen: Yeah. That's a great interaction.
Chris: I even have a blog post. What I mean by big fan, I mean, yeah, it's great that browsers offer it, but I think that it should always be a user consideration.
Stephen: Mm-hmm.
Chris: I don't think you should go throwing around target equals blank on links willy-nilly.
Stephen: No.
Chris: This may have been a case where I would have actually used target blank or-- [Laughter] This is a new -ism that I'm -- not -ism, but a new thing that I've been trying to do.
Like all in CodePen, instead of using target blank, I'll put target equals CodePen documentation.
Stephen: [Laughter]
Chris: Underscore blank doesn't actually mean anything.
Stephen: Mm-hmm.
Chris: It's not a special keyword. It just means you should open in a new tab with that as the programmatic title of the tab. Meaning that if you click the documentation link four times, it will just programmatically change that documentation tab. I know that's a little weird--
Stephen: [Laughter]
Chris: --but I think it's a cool idea. Anyway, I maybe would have considered using target equals blank on a link like that because it does emphasize the idea that you've done it. It really gives you a big checkmark that you accomplished the action that you wanted and allows you to possibly go back to the other tab and see the changes - or I don't know.
But guess what. It's not a link, is it?
Stephen: Yeah, so the whole forking process is just a bit more complicated. There's no just fork URL until an actual fork is created because a fork is based on the current state of the Pen. If you're looking at somebody else's Pen and you make some changes -- say you want to try out some different colors or add a library or fix a bug or whatever -- you're not able to save somebody else's Pen, but you can fork that Pen with your changes intact.
Chris: Yes. Right.
Stephen: There's no link to those changes until that fork is actually created.
Chris: Yeah. They're just temporal. They just exist in the client only. Our database won't take them because you don't own it. Right?
Stephen: Right.
Chris: There can be no URL that represents those changes that you've made.
Stephen: Right.
Chris: So, it's not a link. Not to complicate things too much, but if there was a way for us to force a save and then use a URL-based method, maybe we would have considered that. But I'm not even sure I like that (from a UX perspective). I kind of like the idea that you can go into a Pen, make a bunch of changes, and be like, "You know what? Actually, I'm going to fork this instead of saving it."
Stephen: Yeah.
Chris: Which I kind of like.
Stephen: Yeah. Once you click that button, that's essentially what it's doing. It's saving that as a new Pen (just with a little extra metadata).
Chris: It gives you a chance to abort that first Pen changes, though.
Stephen: Right.
Chris: Which is cool. Because of that, because of this unique thing where it plucks out the state of the Pen and sends it off to a new, you owned Pen, that's a bunch of JavaScript and server-side code interacting to make that happen. Thus, it's a button, not a link. Thus, you can't do the old command click thing on it - until Shaw comes along, apparently.
Stephen: Well, I was thinking about it, like, "How can we solve this?" I was looking at all the code. One interesting tidbit; if you have a form that's submitting with a button click, you can do target blank on a form, which is interesting.
Chris: Yeah. That's true.
Stephen: Our fork setup is not a form. It's not handling things in that way.
Chris: No. You're saying if we could grab all that unsaved state and chuck it into a post request--
Stephen: Right.
Chris: --then yeah.
Stephen: You could. You could target blank a form submission. But how would you do that dynamically? That's a whole other set of problems. But just interesting to note; a form submission can be in a new tab.
Chris: Yeah. Indeed, it can. Indeed, it can. In fact, it gets so nuanced that you could make a button that submits a form, one of which opens in a new tab and the other one does not.
Stephen: [Laughter]
Chris: You can control the form through attributes on the button element.
Stephen: Yeah.
Chris: I just remembered back when I worked at Wufoo and researching all these nuances of how forms work. It gets pretty weird.
Stephen: Yeah. There's a lot of special stuff, even in the JavaScript APIs. You get the inputs mapped to the form element.
Chris: Yeah. That serialization it does to the data. Yeah.
Stephen: Well, so anyway, digging into how forks actually work on the Pen, it's kind of a mixture because we started Reactizing a lot of the websites, so there's actually a React button there that's rendered in the React footer that, when it's clicked, there's some additional logic checking for anonymous users and all that kind of stuff. Then that React button calls an ancient JavaScript class API around Pen saving.
Chris: Sure. Yeah.
Stephen: The specific fork Pen and current state method. And so, I had to wade through with my machete and Indiana Jones hat to figure out where this logic is coming from and where the actual forking happens and when we actually have a URL available to open in a new tab.
Chris: I see.
Stephen: It's all very interconnected with how saving a Pen actually works, like going in there to try and make a change to it is very nerve-wracking.
Chris: But you knew what was there because, at some point, the URL does just change.
Stephen: Right.
Chris: There's probably a document.location= or something in there. Right at that moment, if you're able to pass information all the way from the click all the way to that moment, I guess you could decide whether you're going to document.location or window.open or whatever. You'd have a choice, and you could, theoretically, open a new tab. That was your thesis? [Laughter]
Stephen: Right. [Laughter] Right. But it's going through so many methods, and they're handling data in different ways. Some of it is asynchronous with the AJAX call and the callback and all that. How do you get that data from one point to another, was kind of the biggest hurdle to tackle.
Chris: Was it hacky, or were you able to straight up pass the data in a sensible way?
Stephen: Well, what I ended up doing, so you've got the React button. In that click handler for it, I check for the event.meta key. Have you ever used that value?
Chris: Is it just true or false?
Stephen: Right. It's just like a true or false flag as to whether the user was holding down the meta key, which, on Mac, is command; on Windows, it's control.
Chris: Oh, thank God, Web platform.
Stephen: Right.
Chris: You didn't have to write a little utility method to guess the correct keycode across eight platforms - or whatever?
Stephen: [Laughter] Yes.
Chris: Nope. Just .metakey. Wonderful.
Stephen: Metakey, yeah, and so that's on keyboard events and mouse events and all that kind of stuff. If the user is holding down that meta key, that little flag is true.
Chris: Oh...
Stephen: In that React button--
Chris: Cool, so now you have the data. You just need to scoosh it along.
Stephen: In that React button, where it's calling, like, fork Pen in current state, that long method name for the Pen API. I just pass that flag directly through, like, "Is that true or false?" Then I had to move over to the Pen API and look at that fork Pen in current state method. Taking that flag and just kind of moving it to a top level kind of flag in that class.
Chris: Hmm.
Stephen: Just a little private flag, "Open fork in new tab," basically. Just a little temporary flag that is only used for this method and then, later on, when the Ajax call actually finishes and we finally got that URL for a fork, I can check that flag and use window.open instead of location equals.
Chris: Yeah. window.open is fairly safe these days. Do you think people are going to--? I guess you know what you're doing.
I'm imaging a little popup coming in the URL bar that's like, "This website is trying to open a new tab. Allow or disallow?"
Stephen: Yeah. right. Unfortunately, there's not really another way to open--
Chris: I don't think so either, yeah.
Stephen: --a new one unless you created a link element and added target blank and then forced a click.
Chris: Mm-hmm.
Stephen: But I think browsers are also smart enough to block that (if popups are blocked as well). So, yeah, window.open is really the only option there.
Chris: Yeah, I think so. Somehow, it feels like the operating system can do it. You know when you go to the airport and connect to the wi-fi? Somehow, magically, it's able to pop up this little window, even though you're like, "I've never even been here before. How do you have permission to do that?"
Stephen: [Laughter] Yeah.
Chris: I don't know. Okay, so basically you done did it.
Stephen: Yeah.
Chris: Now it behaves like a link. Maybe not identically.
Stephen: Right. It's not perfect. You can't right-click it. You don't have a deep option like to fork in incognito window or something like that. You know there's a limit here to what we can do with the JavaScript APIs, and it still is actually a button. It's not an actual link.
Chris: Right, so you wouldn't even know if you tabbed to it. You know what I mean? And pressed it there. We can't really communicate to you that--
Stephen: Yeah. There's not any direct user feedback on it. It's just like this is something like a user education kind of thing, but it's also something that we can enhance and figure out a better way to communicate that to the user or just put something in the UI so that they know that that's even an option.
Chris: But pretty rad, and it's on production now, right?
Stephen: Yeah.
Chris: I would think some people are almost naturally experiencing it.
Stephen: Right.
Chris: Like, "I want to fork this," but in my head, I want to do it in a new tab. You might have muscle memory to do that even though it never worked on CodePen, so instead of disappointing those people. Because I think the click would have still gone through, right?
Stephen: Yeah.
Chris: It still would have just forked it. It would just have ignored your request, essentially. [Laughter]
Stephen: Yeah. Right. It's one of those natural things that people just try to do because of muscle memory for working with links and other things. It's kind of an enhancement. It was never broken before, technically, but this just kind of opens up the option for users to do that.
Chris: Pretty satisfying. I like it.
[Guitar music starts]
Chris: This episode of CodePen Radio is brought to you in part by Notion. Notion is so great! Oh, my gosh.
You know it can do so many different things that I don't think it's hard to explain what Notion is, but it's hard to pick what the best thing is. I think perhaps one of the best words is organization.
I've seen individuals take Notion and do some incredible stuff with it; organize their lives with Notion. Have little personal OKRs or track their progress on things or do their calendaring in it or do their to-do lists in it and stuff. It's great at all that stuff. Just the beginning of the iceberg. It's just incredible what people can do with it personally.
But Notion shines best, I think, on teams. And so, when you're talking about teams and organization, I think it's important that people use the tool then. It's not just one more tool to throw at your team that already uses ten different tools, and they're like, "Okay. Now we have another one, I guess." That's not how it's ever worked out for me, and I use Notion on lots of different teams in a big way.
What it does is it becomes the homebase for your team. It becomes perhaps the most important tool. And it's not replacing GitHub, of course. It's not replacing Slack, really. But it's replacing other stuff. It's replacing your to-do things and your calendar things and your Google docs things and your project organization board kind of stuff.
That's how it works out for us at CodePen is that all of our active projects are in there and they're organized in there. Meeting notes are in there. Knowledge base stuff is in there. Content planning stuff is in there.
And it becomes so important that it becomes kind of the first stop of the day, almost, in, like, "What am I doing today? Let me check Notion," because everything that's going on with this business is in there in the form of different types of documents and databases.
It's awesome. Check it out at notion.so.
[Guitar music ends]
Chris: A couple of other things. One of them we've got to talk about is the weird messaging thing that you had to fight to get this to work (just because of UX reasons). But another one is there is actually a URL format for essentially copying a Pen. We think of it as, I guess, the template API. I don't even know if you'd call it that necessarily. But if you're on a Pen that you own, you'll see it in settings.
Any given Pen that you own, you can just flip a switch and make it a template. That way, from the main menu where you're making a new Pen, you'll have a little templates menu. Those pens that you flipped to be templates are just available there. It's for people that are like, "Oh, sometimes I work in GSAP," or whatever. "I'm going to set up a Pen that preloads all the stuff that I normally do for a Pen like that." So, it's just less setup work.
I have a bunch of them. Sometimes, I just know that I want Markdown turned on and want some basic styles in place to style that Markdown, so I have a Markdown template.
Magically, it's a very unsophisticated system that does it. When you start a new Pen, there's just a URL parameter that's like, "Template?" You put the slug of any Pen in there, and it yanks all the settings, all the tags, all the code, all the resources - whatever - for that Pen and makes a new Pen but doesn't save it.
Stephen: Right.
Chris: Like a fork does. You have to save it because you might open that template and be like, "Oh, no. That's not what I meant to do," or something like that. It doesn't force a save on you.
Stephen: Right.
Chris: Because that is URL-based, we could have made that work. We're just choosing not to, in a way, because they're not actually forks. Forks have this other little distinction on CodePen of being "We know if it's a fork or not," and there's so much forking activity that it gives us this opportunity. For example, when you search CodePen, forks are off by default because, if they weren't, a search -- a Pen that nicely matches a search, you might see 85 of them in search results.
Stephen: [Laughter]
Chris: You're like, "That's not useful."
Stephen: Yeah.
Chris: It's nice to know, in certain circumstances, what a fork is or not. Even in CodePen challenges, we don't show forks (by default) in the tab because you'd see so many duplicates that we're trying to show you original work in tags and stuff like that. It's very good to know.
It's not like we're trying to hate on your fork. [Laughter]
Stephen: No.
Chris: The benefits of knowing whether a Pen is a fork or not is pretty strong.
Stephen: Well, it's kind of two different intentions. A template is, "I want to use this as a starting point to build off of. This is kind of my scaffolding that I'm going to build on." Whereas forking, a lot of people use it for almost like bookmarking.
Chris: They do. Right.
Stephen: Which is not ideal. We don't want to just store copies of a Pen for somebody to bookmark. That's really what the heart button or the love button is for.
Chris: True, or collections are for.
Stephen: Or collections. That's a great way to save a Pen for later. But forking--
Chris: Even pinning is similar. There are all kinds of ways you can save a Pen. But, yeah, forking is a funny way to do that, and we have all the evidence in the world that people do that exactly for that.
Stephen: Yeah.
Chris: Like, "I want my own copy of this," for some reason. Not even because they want a copy, but just because they want to be able to find it again quickly, so they do that. I hate to tell everybody this because I'm sure you could disagree with our product decision -- although, not a first -- that's one of the reasons we moved it to the footer.
Stephen: Mm-hmm.
Chris: We're not trying to disappear that function, but it used to be in the header and people just forked the crap out of stuff when it was in the header.
Stephen: Yeah.
Chris: We know, through user research, it's like, "Why are you doing that?" And it was overwhelming, "I just want to be able to find it again."
Stephen: [Laughter]
Chris: We're like, "Oh, but we have three other first-class citizen ways for you to do that."
Stephen: There's a lot of pushback when we made that change initially.
Chris: There was!
Stephen: But people are used to it now, like people that actually care about forking. They know where it is and how to use it.
A fork, it's so much more than a template. You want a copy of it as it currently is, with changes.
Chris: Right.
Stephen: I want to modify this existing thing to make it something different.
Chris: Yeah. Yeah.
Stephen: Is kind of that fork intention. But, yeah, as I was digging into this, going back to that initial problem of, "Did I fork it? Am I working on a copy of this, or am I working on the original? Did it actually save?" that kind of unclear status of it.
Chris: Yep.
Stephen: As I was testing it out, I realized our messaging -- we have the nice little Toast Messages that pop up. We're supposed to have one that says, "Forked this Pen," or whatever. "Pen forked," I think it says.
Chris: Yeah. "Success event for your fork." Was it just not working?
Stephen: It was not.
Chris: Cool.
Stephen: And so, looking at the console, when you clicked "Fork," right before the page navigated, there was this big error that came up in the console.
Chris: Oh...
Stephen: And so, I was able to trace that down.
Chris: Oh, right before the URL because the page reloads.
Stephen: Right.
Chris: You wouldn't have a chance to really see that. Oh, that's funny. You'd think it'd end up in Sentry or something. But, yeah, nobody is going to report -- a user is never going to be like, "I think there should be a green Toast Message after--"
Stephen: [Laughter]
Chris: You'll never hear about that from a user, so you've either got to write a test for it, watch for some kind of error event in your error reporting stuff, or just never know, which apparently we chose option three there.
Stephen: Yeah. Yeah, so I knew it was supposed to be there, and I saw this error, and so I was able to suss it out. But our whole messaging system for when the page actually changes, because the code base was built up over so many years and things are still actual page changes, it's not like a full, single page application where everything is just AJAXed in and injected and all that.
Chris: Yeah. Not yet.
Stephen: We don't really have a persistence between one page and another, so we have a special little messaging system set up that, somewhere along the way, we were trying to inject an actual JSX rendered message, and things just got a little squirrelly with how we were storing those messages. [Laughter]
I cleaned all that up and found a bunch of duplicate code and unnecessary code. It became this whole big cleanup where even the delete button, that uses that same messaging system because, once you delete an item, you get navigated away back to your work - or whatever - and get a confirmation message that says, "Hey, it was deleted." That was broken too, so I fixed that and unified the delete buttons across Pens, projects, action menu, and all this kind of stuff.
Chris: Yeah. Yeah. Ain't that the way? You know?
Stephen: [Loud exhale]
Chris: Pretty much anything, you know, on projects of a certain scale, age, and stuff that you think you can just go in there and write "if meta key, window.open."
Stephen: [Laughter]
Chris: Sorry.
Stephen: [Loud exhale]
Chris: No.
Stephen: No.
Chris: [Laughter] It ends up being--
Stephen: Just make it a link. No.
Chris: --a day project kind of thing. Yeah. Yeah. But pretty clean in the end, I guess, by our standards. It could have been worse kind of situation.
[Laughter]
Chris: Could have been either impossible or such a major rewrite that it just wasn't in the cards - or whatever. But did it.
I do think it's kind of a cool system. It uses local storage, right? It dunks it in local storage and then it doesn't show it on the current page. But if the page of another page loads, it looks in there and be like, "Do I have any messages that CodePen of the past has told me to display to the user? Yes? Oh, let me do that."
Stephen: It's a little mini inbox.
Chris: An inbox.
Stephen: Just text messages.
Chris: Indeed.
Stephen: Yeah.
Chris: Indeed. Kind of a cool usage for local storage, I think.
Stephen: Yeah. It's not a perfect system like if you deleted something and navigated away. You might get that local storage message.
Chris: In a weird place or something. Yeah.
Stephen: Days later or whatever.
Chris: [Laughter]
Stephen: You know. It's--
Chris: Probably not. That would be weird to see. But, yeah, I could see it. If you close the tab really super too fast.
Stephen: Yeah.
Chris: There'd be one that opens - or something like that.
Stephen: Right. It's possible. But you know this is all stuff, you know, in looking at it and how forks work and how users are using forks. As we continue to build the product and change things around, these are the considerations that we've taken into place and iterate and make better.
Chris: I've seen apps that just chuck it in the URL. You know?
Stephen: Hmm.
Chris: The URL just becomes ?success=true, or something, and you're like, "True? Thanks!" It's so funny to mess with that.
Or they put the entire user message in the URL. [Laughter] Then you can hack it and make it--
Stephen: Yeah. This is a little cleaner - a little cleaner.
Chris: I think it's a lot cleaner. Yeah. That's kind of a cheesy way to do it.
Stephen: Yeah. While I was doing that, we had the existing local storage system, and it was very basic. I basically upgraded it a little bit, so now we can actually pass through success or error messages from one page to another if need be, so it's a little more enhanced.
Chris: It's to the metal, right? It's just using the platform to do it.
Stephen: Mm-hmm.
Chris: There's probably - I don't know - we could have used the database or something.
Stephen: Yeah.
Chris: But it's like, hmm... You know? [Laughter]
Stephen: [Laughter]
Chris: Not my favorite. On the pages that are involved (for the Pen editor) is still in Rails.
Stephen: Mm-hmm.
Chris: We do hope to change that at some point, but there's still some Rails rendered pages of CodePen.
Rails had a whole thing for this. We used to use this all the time in CodePen. They were called Flash Messages.
Stephen: Hmm.
Chris: It had a little special thing, and all the templates had a little piece of HTML at the top that was like, "If Flash Message, then show--" whatever. You could put whatever HTML in there, and we'd kind of design the HTML to have a little yellow bar.
The point was, it would say, "Blog post successfully updated," or whatever, because Rails was famous for CRUD type of applications, so all those Flash Messages were success or error messages and stuff. It was kind of a cool system, but if you're not all in on one framework (like we are not), you can't use that stuff anymore.
Stephen: Right. We wouldn't be able to trigger those Flash Messages from React. I think we had some--
Chris: No, probably not.
Stephen: I think we used the same styles and some complicated integrations to get a similar thing until we moved everything to these Toast Messages.
Chris: Yeah. Yeah, that's kind of the irony. The core of the Pen editor is Rails rendered, but not the header and not the footer and not any other little bonus stuff that we do. When we moved to those Toast Messages, we were like, "We're not going to write it twice," so we just kind of forced those to work in React.
Stephen: Yeah.
Chris: Anyway, so close. So close.
Stephen: [Laughter]
Chris: [Laughter] Anyway, well, thanks for the idea, Cassie, and thanks for the work, Shaw, because it does now work. Now it's our job to communicate it, which I guess this podcast will reach 17 of you.
Stephen: [Laughter]
Chris: But if all of you tell your 17 friends and they tell their 17 friends, then we pretty much got the CodePen code base covered - or our user base. It's the power of multiples there.
But no, really, we'll have to -- we'll update the docs, and we'll attempt to call it out to some degree because people have muscle memory. CodePen is old. Y'all already know how to use it. So, when we change it, we've got to tell you.
Stephen: Yeah. It's engrained.
Chris: Cool. See you next time.
Stephen: Bye.
[Radio channel adjustment]