-
-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Point a navigation entry to a "bookmark" global #382
Comments
That would certainly present a unique situation for the Navigation plugin. Currently, we only accept relating elements themselves, whereas in your case, you want to pick an element field within a global, and use that value. That might prove to be a technically challenging one, due to the different type of element fields. Then, we'd also need to look at other fields like the 4 populate link fields, and I'm sure there's more field types. If we're talking about being able to use shorthand Twig in the "Custom URL" node type, then that's something we can certainly look into. It's not the most novice-friendly option, but certainly okay for developers. You could also create your own node types to allow you to pick your global fields as well. As for the comment about the node being the source of truth, it might not be the best fit, as you say there's no real identifying trait for a node other than the ID. However, you could add a custom field for a "handle" to your node, and then search based on that custom field in an element query for a node ( |
I'm actually not using twig at all -- we're using Craft in headless mode, and have a separate application which fetches data via GraphQL. I think a custom node type may do what I want. I didn't realize that feature exists. I'm trying it out now... It looks like the idea is that I make a new node type, then in the navigation settings I add some custom fields, and set them to only appear if the node type matches my custom one, right? At the moment if I choose my new "bookmark" node type I don't see my custom field in the sidebar: ("This is the settings HTML" is output by my node type for now in Even though that field is set to required, it's letting me create the node without it. Is there a way to have my custom field appear in this sidebar? Or am I meant to add something in my If I then click to edit that node, I do see the custom field I added, along with the Trying to save the node without setting my custom field here is correctly giving an error saying it's required. Letting it save without it in the first place opens the possibility of bad data. Is that a bug or have I missed a step, or is there something I'm not understanding? |
So with the above issues still standing, I have this working. My PHP class looks like this. <?php
namespace mynamespace;
use verbb\navigation\base\NodeType;
class BookmarkNavigationNode extends NodeType
{
public static function displayName(): string
{
return "Bookmark";
}
public static function hasTitle(): bool
{
return true;
}
public static function hasUrl(): bool
{
return false;
}
public static function hasNewWindow(): bool
{
return true;
}
public static function hasClasses(): bool
{
return true;
}
public static function getColor(): string
{
return "#a0522d";
}
public function getUrl(): ?string
{
try {
// This is an "entries" field which accepts maximum 1 entry
$bookmark = $this->node->getFieldValue("bookmark")->one();
} catch (\craft\errors\InvalidFieldException $e) {
// I don't know if catching this is overkill
return null;
}
if (!$bookmark) return null;
try {
// This is a "link" field (sebastianlenz/linkfield)
$link = $bookmark->getFieldValue("linkDefault");
} catch (\craft\errors\InvalidFieldException $e) {
// I don't know if catching this is overkill
return null;
}
if (!$link) return null;
return $link->url;
}
} And in my main module file I have init code very similar to the example given at https://verbb.io/craft-plugins/navigation/docs/developers/extensibility#node-types So I can get the URL directly off these nodes, or I can drill deeper into the custom field if I want the specifics, which sometimes I do. For example, if it's an entry, I want to know its site ID so I can tell for sure whether it's the same site, because if so I can do a SPA-style navigation to it rather than a full page navigation. |
Sorry, I may have explained that incorrectly. Custom fields are for all node types, and are managed in the Navigation settings, under Node Fields. You won't see then in the right-hand sidebar when you add a node, but once added you can add your content there. You could add your custom field to the sidebar, but it does depend on what sort of field it might be. For example the site node type adds a select field, that's for the {{ forms.textField({
label: 'Custom Field' | t('app'),
id: 'myCustomFieldHandle',
name: 'fields[myCustomFieldHandle]',
}) }} That's just for a text field, but as I mentioned, it depends what sort of custom field you want to include. But, as you're using a custom node type, you can technically add whatever bits of content you want, and you've got control over the template when adding a new node, so include any sorts of fields your node type requires. |
It's not exactly for all node types since I can use the condition to only show it on one given node type. But yeah, I do not like how it's not showing it in the sidebar, and allowing the node to be saved without the required field being filled.
What if it's an "entries" field, like I want to use? (I said before I was doing this via globals, but I'm toying with the idea of bookmarks being entries.) Your example looks simple, but does just printing a field there somehow magically also handle saving and retrieving the data? |
Yeah, good call on any required fields not being provided when created. We'll probably need to change the node-creation sidebar to show the element slideout instead. We just wanted to keep the process of creating nodes simple, particularly when trying to add multiple elements in a single go. That example code was just to put in your If it's an element field, you can use Craft's {{ forms.elementSelectField({
label: 'Custom Field' | t('app'),
id: 'myCustomFieldHandle',
name: 'fields[myCustomFieldHandle]',
elementType: 'craft\\elements\\Entry',
}) }} As for saving the data, the entire form for creating a node is serialized and used for the node. Retrieving isn't really an issue, as you're creating the node from scratch. Editing the node in the element slide-out will be a different case and that'll just be done automatically, based on the navigation's node type fields. |
I'm giving that a try. It couldn't find my template and so I added this to my module: Event::on(
View::class,
View::EVENT_REGISTER_CP_TEMPLATE_ROOTS,
function(RegisterTemplateRootsEvent $event) {
$event->roots["myapp"] = __DIR__ . "/templates";
}
); For {% import "_includes/forms" as forms %} It seems these form helper macros are undocumented, but after some web searches I found that I can add the option public function beforeSaveNode(bool $isNew): bool
{
$bookmark = $this->node->data["fields"]["bookmark"];
if (!$bookmark) throw new \Exception("A bookmark must be chosen.");
return true;
} (It's unclear what the bool I'm supposed to return is for, by the way. It seems to save the node whether I return true or false.) One place where I'm stuck now is limiting the available options to entries in a particular section. I found an old SO post which suggests I can do I noted that the field edit screen in Craft actually calls them "sources" rather than "sections" so I found this other old SO post which has set Any idea? My workaround for now is not not ideal; it's to just throw an error if the wrong type was chosen, by putting this in the $bookmarkId = $bookmarks[0];
$bookmark = \craft\elements\Entry::find()->siteId("*")->section("bookmarks")->id($bookmarkId)->one();
if (!$bookmark) throw new \Exception("A bookmark entry from the bookmarks section must be chosen."); As much as I don't like this, there's a bigger problem: The entry I chose is not actually saved in the custom field. I can pick an entry in the sidebar, hit save, the validation I wrote accepts it (and I can verify via debugger that it has the correct ID in memory), but then when I go to edit the node afterwards, the field is empty. To give a bit more context, the custom field has handle {% import "_includes/forms" as forms %}
{{ forms.elementSelectField({
label: "Bookmark" | t("app"),
id: "bookmark",
name: "fields[bookmark]",
elementType: "craft\\elements\\Entry",
required: true,
}) }} |
I've discovered why I shouldn't continue exploring late at night. I was totally overthinking this. I can simply use an "entry" node, and point to my bookmark entry. It doesn't need to be a special type limited to bookmark entries. I don't really need Navigation to resolve the nested link in that bookmark entry and present it in the node's I do still think solutions to the issues I had above would be valuable in general. I'll leave it up to you whether to leave this open or close it. Thanks for all you help. |
Yeah, sorry for the vague templating guidance, that's getting into Craft native territory (their forms macros), and properly bootstrapping a module with the template roots stuff. But you've certainly given me pause for thought about making the add-node behaviour consistent with how you edit it. That way, things like this would be much easier to address. I may even make the adding screen configurable separate to the "Node Fields" for editing, just to allow people to customise (and minimise) the fields used for adding a new node. I believe that would've helped your use-case by adding a custom Entries field, being able to add that to the "Node Fields" layout, configuring that to the "Add Node" screen and it'll all just work. Of course, with a bit more documentation as well. |
What are you trying to do?
We have a few URLs which are used throughout the app. They might very infrequently change, but if they do they should always stay in sync. We've done this with "globals". A navigation entry is one of the places we want to point to one of these URLs.
What's your proposed solution?
If the navigation plugin were to accept a plain text global as another source of "external URL", that would work for us.
But it might be nice as a bonus if it'd support a link field global so it could be flexible in type. Or a similar type provided by the Navigation plugin.
Additional context
Another possibility I've been considering is to treat the navigation entry as the main source of truth for the URL, and so instead of fetching it for other contexts from globals, fetch it from the navigation. This might work in theory but I'd need some kind of reliable identifier with which to find it. I don't want to use an ID since this would change if the link gets deleted and recreated. I don't want to use the title because that might change. Using slug might make sense but it only seems to be
null
or"1"
on the nodes I have, and I don't see a field to manually set it.The text was updated successfully, but these errors were encountered: