Having tried a bunch of Umbraco packages providing custom property editors for creating links in the back office, I got a bit tired of the underlying complexity and potential incompatibility with future Umbraco versions. Not that they weren't good - most of them were. It's just that I prefer simpler solutions.
Next thing I tried was document compositions (for single links). It wasn't hard to implement a new tab that would contain a content picker, a textbox for external URLs and a checkbox to allow the link to open in a new window. Even wrote some code around that so I wouldn't have to copy the same code over and over again. But still, it wasn't the best solution. I admit I used such an approach on the Umazel Starter Kit, but during development of version 2.0 (still not finished at the time this post was written), I needed a better solution.
So I thought: "Why not use Nested Content for that, now that it's in the core?". Nested Content is guaranteed to keep working on future Umbraco versions (well, until big breaking changes are introduced, at least), and it's simple to use either for one or multiple items. No other dependencies except my own code.
The next question that came up was "how can I define ALL the attributes a link could potentially have so that I can just produce the full markup from code if needed?" I have used links that needed ALT and TITLE attributes, others that only needed a caption, and others that needed additional CSS classes and/or custom attributes. So I needed something that could store all of this info. Nothing simpler that a Nested Content document type and a custom data type to hold it. So NcLinkItem was born. The extra attributes were implemented as a Nested-Content-Inside-Nested-Content field, where you can define name/value pairs, whatever you need, as many as you need.
But hold on a second. How many types of links are there? For one, there are your usual text-only URLs - just a textbox where you type your link and that's it. How about internal links (links to other pages on your Umbraco site)? How about internal links with querystring parameters and/or anchors? How about links to files?
So a more complex structure was needed to deal with unique properties belonging to each link type. Enter composition, with the AbstractLinkItem document type containing all the common properties, and then composed with three distinct doctypes - one for external links, one for internal links, and one for files.
Having all the structure in place, I could now create my data type(s) for a single link or a collection of links. In my case, those are Custom_NcLinkItem_Single and Custom_NcLinkItems_Multiple (yes, I know, "single" and "multiple" words are redundant, but it's a convention that makes it easier to see and select). Add this to a doctype and presto, links are ready to use.
But "use", how? I also needed some way to quickly parse the data and have it ready for my markup. So the GetLink() extension method was born.
The GetLink() extension method returns a LinkItem object which has all the properties in place for you to use, i.e. a model for the link. Moreover, there's the GetLinkMarkup() extension method which parses the link and creates the full HTML ready for you to use. For example, you can have a link like this rendered with a single call to that method:
<a href="/myhomepage/mycontentpage?param1=1¶m2=2" target="_blank" title="my title" alt="my alt" class="myclass" style="mystyle" data-blahblah="whatever">My Caption</a>
So let's suppose you have added this construct to a document type and want to render links. You can do one of the following (assuming you only want to render the first link). In this example it is assumed you have named your new property "links:
(The namespace is DotSee.Common.Link, so you have to adjust your @using (s) accordingly).
LinkItem link = MyDocument.Links.First().GetLink();
LinkItem link = Model.Content.GetPropertyValue<IEnumerable<IPublishedContent>>("links").First().GetLink();
This will give you a LinkItem object with all the relevant properties in place. The Url property is obviously the most important - depending on whether you have entered an internal link (with parameters and/or anchor), an external link or a file link, the Url property will be automatically calculated. The full set of properties you can use in your Razor templates are:
- Url: The link Url, together with any querystring parameters / anchors in case of an internal link.
- UrlSecure: The same Url, prefixed with "https://" even if it has originally been prefixed with "http://"
- UrlNoHttp: The same Url without the protocol prefix.
- Target: "_self" or "_blank" depending on whether the checkbox has been checked or not
- Caption: The contents of the caption field
- AltText: The alt text attribute value, defaulting to the caption field value if the relevant checkbox has been checked.
- TitleText: The title attribute value, defaulting to the caption field value if the relevant checkbox has been checked.
- InternalLinkId: In case of an internal link, the page id, otherwise zero.
- OtherAttributes: A NameValueCollection of other attributes that have been specified.
- IsInternal: Boolean indicating whether this is an internal link.
- IsFile: Boolean indicating whether this is a file link.
On the other hand, if you don't want to use those properties separately, you can skip all of the above and render your HTML markup directly, by using the following:
This will create a LinkItem under the hood and compose the markup including url, caption, target, alt/title attributes, and any extra attributes specified, returning you the full markup that is ready to use in your Razor template.
You can download the package from here: https://our.umbraco.com/projects/backoffice-extensions/a-link-to-rule-them-all/
Source code is available here: https://github.com/sotirisf/Umbraco-Link
Hope you find it useful. Happy coding!
UPDATE: After I posted this on the Umbracians Slack channel, I was asked the following:
"Not entirely sure what you gained over just using something like https://github.com/rasmusjp/umbraco-multi-url-picker"
A good question indeed - here's my answer:
It's a matter of preference. I prefer to have to deal with core datatypes (although MUP is based on a core datatype). Also:
1. Support for additional attributes like alt and title and setting default behaviours if not filled.
2. Support for additional tags without having to do that in code.
3. Support for querystring parameters and anchors for internal links
4. One-method-call HTML rendering with all of the above (optionally).
Let me also add that although it looks pretty simple, this thing evolved over time based on clients' requests. At one point in time, I thought it was about time to make it reusable. It's served me well, so I thought why not blog about it and create a package.