Hello, my friends 🙂 So this post will be part 2 of the previous post – Sitecore Forms and GraphQL in harmony – building custom control (Part 1).
Before we start, I must emphasize the importance of Blazor – the game changer of the decade! It will allow you to build a single-page web app in c# that runs in the browser with WebAssembly – no need for javascript anymore. In the ASP.NET Core 3.0 release, Microsoft will have a (pre)server-side version of Blazor(called Razor components) using client-side with SignalR.
I’ve setup a client-side Blazor Sitecore project at GitHub – SitecoreBlazor. Please check out my post Time travel into the future – BLAZOR + SITECORE + HELIX.
Blazor futurum sit(Blazor is the future)
Ok, guys. In the previous post, Sitecore Forms and GraphQL in harmony – building custom control (Part 1), we did the editor/admin part of the custom Sitecore Forms control – Tree View List.
Let’s continue 🙂
If you remember from the previous post, we choose the jQuery component – Multi-Select Drop Down Tree Plugin With jQuery – Combo Tree. Next will be to put the js file comboTreePlugin.js (from the jQuery component) in our solution:
Foundation\SitecoreForms\code\sitecore modules\Web\ExperienceForms\scripts\comboTree\
We will have to do some minor changes in the comboTreePlugin.js file. Right now it expects the JSON data to look something like this:
[ { id: 1, title: 'choice 1', subs: [ { id: 10, title: 'choice 1 1' }, { id: 11, title: 'choice 1 2', subs: [ { id: 110, title: 'choice 1 2 1' }, { id: 111, title: 'choice 1 2 2' }, { id: 112, title: 'choice 1 2 3' } ] } ] } ]
The JSON we get from our GraphQL query will differ a bit, instead of subs we will have children. That means we need to make a small change in the comboTreePlugin.js file. In fact, it’s in the ComboTree.prototype.createSourceItemHTML method:
ComboTree.prototype.createSourceItemHTML = function (sourceItem) { var itemHtml = "", isThereSubs = sourceItem.hasOwnProperty("subs"); itemHtml = '<LI class="ComboTreeItem' + (isThereSubs?'Parent':'Child') + '"> '; if (isThereSubs) itemHtml += '<span class="comboTreeParentPlus">−</span>'; if (this.options.isMultiple) itemHtml += '<span data-id="' + sourceItem.id + '" class="comboTreeItemTitle"><input type="checkbox">' + sourceItem.title + '</span>'; else itemHtml += '<span data-id="' + sourceItem.id + '" class="comboTreeItemTitle">' + sourceItem.title + '</span>'; if (isThereSubs) itemHtml += this.createSourceSubItemsHTML(sourceItem.subs); itemHtml += '</LI>'; return itemHtml; };
We will replace subs with children.
Great! The js file is now done for the jQuery component. Next will be to put the js file in the “MVC Form” rendering. More specifically, field “JavaScript assets” in the “Assets” section.
*In post Create custom controls in Sitecore Forms without customization, I introduced another approach of adding js and css files to Sitecore Forms by using Sitecore.Foundation.Assets.
What’s left is to put some magic into it, some Sitecore GraphQL magic 🙂
So GraphQL, what is it? Well here is a great site where you can read all about it – Introduction to GraphQL. But basically it’s a query language for APIs(not databases).
Anyway, Kam Figy(at Sitecore) did a great presentation last year at SUGCON EUROPE 2018(in Berlin). You should check it out – Uber Modern APIs for Sitecore
Sitecore GraphQL is a modern query language to the Sitecore API. GraphQL is a query language for the content API, designed to be the ultimate API for a solution that has the need to efficiently consume content without over-fetching. Sitecore GraphQL is for a developer easy to work with as it’s a self-documenting and strong typed API with amazing graphical tooling available.
*Read all about it(from Sitecore’s own Pieter Brinkman) – Full headless experiences with Sitecore Omni.
You should also check out the JSS stuff – Sitecore JavaScript Services. Build Headless JavaScript applications with the power of Sitecore. But if you don’t want to use javascript but still want to do headless web app’s, you should look into BLAZOR 😉
Apollo seems to be the most popular “framework/library” when working with GraphQL.
In this case, Apollo is a bit too much. Instead, I found a very nice little library that is perfect for us – graphql.js: lightweight graphql client. That is the one we are going to use 🙂
First, we need to install it(get a js file and put it somewhere). Since this is a Habitat solution, it means we are using bower. So locate the Sitecore.Foundation.Theming project and do the:
bower install graphql.js --save
*Yes, Bower is old and outdated. But things are happening in the Habitat world – Issue 355. Please feel free to contribute dear frontenders 🙂
We also need to update the bundleconfig.json file(in Sitecore.Foundation.Theming) and add the bower_components/graphql.js/graphql.js:
[ { "outputFileName": "scripts/Sitecore.Foundation.Frameworks.js", "inputFiles": [ "bower_components/modernizr/modernizr.js", "bower_components/jquery/dist/jquery.js", "bower_components/jquery-ui/jquery-ui.js", "bower_components/jquery-validation/dist/jquery.validate.js", "bower_components/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.js", "bower_components/bootstrap-sass/assets/javascripts/bootstrap.js", "bower_components/graphql.js/graphql.js" ], "sourceMap": true } ...
Ok, so we have the graphql.js in place. Next will be to make the habitat website “graphql” friendly. Let’s dive into the JSS documentation and locate The Sitecore GraphQL API.
We need to configure a GraphQL endpoint. We will make it easy for us and enable the Sitecore.Services.GraphQL.Content.Master.config in the your_site\App_Config\Sitecore\Services.GraphQL folder.
If you want to make sure it works, you can test it out in the “graphql” UI(http://your_site/sitecore/api/graph/items/master/ui). To enable it, please do the following:
Set <compilation debug=”true”> in the web.config if it isn’t already. This will enable the GraphQL GUI with the default security settings. For security, the GUI is disabled for production scenarios by default.
Now we have GraphQL up and running on our website, thank you JSS people for this lovely contribution. Next thing will be to create a JS file for our “Tree View List” component and put some graphql love to it 🙂
We will call it SitecoreForms.js and put it in our project (in folder: Foundation\SitecoreForms\code\Scripts\SitecoreForms).
const graph = graphql("/sitecore/api/graph/items/master", { alwaysAutodeclare: true, asJSON: true, debug: true }) graph.fragment({ itemExtensions: { itemData: "on Item {id, title: name}" } }) const itemDescendantsFixed = graph(`query ($path: String!, $childrenTemplate: String!) { item(path: $path ) { children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData } } } } } } } } } } } } } } } } } }`) let loadTreeViewList = function(tree){ itemDescendantsFixed({path: tree.data("rootnode"), childrenTemplate: tree.data("childrentemplate")}).then(function (response) { tree.comboTree({ source: response.item.children }); console.log(response.item); }).catch(function (error) { console.log(error); }); }; jQuery(document).ready(function() { var tree = jQuery(".comboTreeWrapper"); if(tree){ loadTreeViewList(tree); } });
Let me give you a detailed explanation.
Here will we create a connection to the GraphQL endpoint.
const graph = graphql("/sitecore/api/graph/items/master", { alwaysAutodeclare: true, asJSON: true, debug: true })
Ah fragments, I love fragments. You can do some very cool things with fragments. Fragments let you construct sets of fields and then include them in queries where you need to. Here we will look at the item object and return id and title(alias for the name). We are also doing the fragment modular, instead of adding it directly when we connect to the GraphQL endpoint(above).
graph.fragment({ itemExtensions: { itemData: "on Item {id, title: name}" } })
Here is the graphQL query, with two parameters – the item id(or path) and the child template id.
Since it’s not possible to do a recursive query of unlimited depth(in GraphQL), we will instead have a fixed number of “descendant/children” levels. The method children(includeTemplateIDs: [$childrenTemplate]) will give us children using specific template/s. Notice how we use the fragment, itemExtensions.itemData.
const itemDescendantsFixed = graph(`query ($path: String!, $childrenTemplate: String!) { item(path: $path ) { children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData children(includeTemplateIDs: [$childrenTemplate]) { ...itemExtensions.itemData } } } } } } } } } } } } } } } } } }`)
The loadTreeViewList method will do the GraphQL call.
let loadTreeViewList = function(tree){ itemDescendantsFixed({path: tree.data("rootnode"), childrenTemplate: tree.data("childrentemplate")}).then(function (response) { tree.comboTree({ source: response.item.children }); console.log(response.item); }).catch(function (error) { console.log(error); }); };
In then(function (response)… we will handle the response from the GraphQL query and set the JSON result response.item.children to the jQuery “Tree View List” component.
*Here is how the JSON looks like from response.item.children(If we have set the root node to the Home item and the children template to “Page Type” template Section).
[{ "id": "762684E7480B41D682DE8549BABE95E3", "title": "About Habitat", "children": [] }, { "id": "8A80477E7CB44CEEA035B48AC118ABE8", "title": "Modules", "children": [{ "id": "8D8EDB18B6414277BAD39C6CF89F9FE3", "title": "Project", "children": [] }, { "id": "F7434DF7E32E4F8CA50E27C476F4DC32", "title": "Feature", "children": [] }, { "id": "1A3560FC9E544E65BEADE349F3B80DD7", "title": "Foundation", "children": [] } ] } ]
At last, we now have a “Tree View List” component showing some lovely GraphQL data 🙂
Pretty cool stuff! That means that GraphQL is not only for JSS app’s, we can also use GraphQL in “classic” MVC web app’s.
That’s all for now folks 🙂