I looked at you github example David and while its kinda what i do have already (if i strip out the bootstrapper view model and the dojo/dijit script stuff from my layouts). The global menu is static and i cant get ut to pull up like in the cms edit view, its static like in the cms admin view.
Also i would like to have the epi dockable panels support in my views possible, eg the content tree panel and media/block panel if at all possible.
Hello Mattias
I am not sure what your ultimate goal is but if you want to take full advantage of the pinnable panels etc then it's a lot of Dojo work.
However you want to edit, view and use custom data from an external source in EPiServer then you can do worse than looking into content providers. Per wrote an excellent post about them.
My primary goal is to have controllers that display my views but i want it to look built in with epi. Sure that i kinda have acomplished, but i still want to utilize more of the built in epi ui/ux and make stuff look like epi. Be able to us pinnable panels would be icing on the cake really. Ive spent many hours googling but cant find any information how to make work of the dojo/dijit stuff to get stuff working.
Later on i will also laborate with the cusom content providers, for adventure works products tables to show products on the epi pages. So i thank you for that post fom Per.
Does anyone know how to make use of the built in pinnable panels for your own views ? Utilizing the built in shell modules and dojo/dijit and the built in scripts for shell functions.
I have played around with find and did some reflecting and came up with this. If i created a view the BootstrapperViewModel goes trough and it works kinda good.
I had to add it as a module in web.config too otherwize it cant find the module. This is the new stuff i got now.
<episerver.shell> <publicModules rootPath="~/modules/" autoDiscovery="Modules" /> <protectedModules rootPath="~/EPiServer/"> <add name="Shell" /> <add name="CMS" /> <add name="EPiServer.Packaging.UI" /> <add name="Aw" /> <!-- My custom module --> <add name="Find"> <assemblies> <add assembly="EPiServer.Find.Framework" /> <add assembly="EPiServer.Find.UI" /> <add assembly="EPiServer.Find.Cms" /> </assemblies> </add> </protectedModules> </episerver.shell>
Composite view for my controller view, to load dojo/dijit stuff, and to get a BootstrapViewModel.
using EPiServer.Shell.ViewComposition; using EPiServer.Shell.ViewComposition.Containers; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Epi8Sandbox.CompositeViews { [CompositeView] class PersonView : ICompositeView { IContainer rootContainer; public string DefaultContext { get { return ""; } } public string Name { get { return "/global/adventureworks/person"; } } public IContainer RootContainer { get { if (rootContainer == null) { CustomContainer container = new CustomContainer("epi-aw-bootstrap/BootstrapPane"); container.Settings.Add("id", (object)(this.Name + "rootContainer")); rootContainer = (IContainer)container; } return rootContainer; } } public string Title { get { return "AdventureWorks Person"; } } public ICompositeView CreateView() { return new PersonView(); } } }
And this is my current controller:
using EPiServer.ServiceLocation; using EPiServer.Shell.Modules; using EPiServer.Shell.Navigation; using EPiServer.Shell.Web; using EPiServer.Shell.Web.Mvc; using log4net; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace Epi8Sandbox.Controllers.AdventureWorks { [MenuSection("/global/Aw", Text = "Adventure Works", SortIndex = 10000)] [MenuItem("/global/Aw/Person", Text = "Persons")] [Authorize(Roles = "CmsEditors, CmsAdmins")] public class PersonController : Controller { readonly ILog Log = LogManager.GetLogger(typeof(PersonController)); private readonly IBootstrapper _bootstrapper; public PersonController() { _bootstrapper = ServiceLocator.Current.GetInstance<IBootstrapper>(); } // GET: Person public ActionResult Index() { ViewBag.ViewTitle = "Person.Person"; BootstrapperViewModel viewModel = null; try { viewModel = this._bootstrapper.CreateViewModel("/global/adventureworks/person", this.ControllerContext, "Aw"); viewModel.GlobalNavigationMenuType = GlobalNavigationMenuType.PullDown; } catch (Exception e) { Log.Error(e); throw e; } //viewModel = new BootstrapperViewModel //{ // GlobalNavigationMenuType = GlobalNavigationMenuType.PullDown, // UseContainers = false, // ViewTitle = "Person.Person", // ViewName = "/global/Aw/Person", // ModuleName = "/global/Aw", // Modules = new List<ModuleViewModel>() //}; return View(viewModel); //return View(); } } }
My Layouts:
_Admin_Root.cshtml
@model BootstrapperViewModel @{ Layout = null; } @using EPiServer.Framework.Web.Mvc.Html @using EPiServer.Framework.Web.Resources @using EPiServer.Shell @using EPiServer.Shell.UI @using EPiServer.Shell.Web.Mvc @using EPiServer.Shell.Web.Mvc.Html @using EPiServer.Shell.Navigation @{ var dojo = new DojoConfig { ParseOnLoad = false, Async = true, IoPublish = true }; } <!DOCTYPE html> <html class="dj_webkit dj_chrome dj_contentbox has-webkit has-no-quirks"> <head> @* Set IE=edge to make IE use its best rendering mode instead falling back to compatibility mode based on intranet zone, etc. *@ <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <title>@Model.ViewTitle</title> @Html.Raw(ClientResources.RenderResources("ShellCore", new[] { ClientResourceType.Style })) @* //TODO: Should only be loaded by the dashboard *@ @Html.Raw(ClientResources.RenderResources("DojoDashboardCompatibility", new[] { ClientResourceType.Style })) @RenderSection("HeaderContent", false) </head> @*<body class="<%: Html.ThemeClass() %>">*@ <body class="Sleek epi-mouse-focus"> @Html.Raw(Html.ShellInitializationScript()) @RenderBody() @Html.Raw(DojoConfigurationHelper.ConfigureDojo(null, dojo, true)) <script type="text/javascript" src="@Paths.ToShellClientResource("clientresources/dojo/dojo.js")"></script> <script type="text/javascript" src="@Paths.ToShellClientResource("clientresources/epi/epi.js")"></script> @Html.Raw(ClientResources.RenderResources("DojoDashboardCompatibility", new[] { ClientResourceType.Script })) @RenderSection("ScriptContent", false) </body> </html>
_Admin_Bootstrap.cshtml
@model BootstrapperViewModel @using EPiServer.Framework.Web.Mvc.Html @using EPiServer.Framework.Web.Resources @using EPiServer.Framework.Serialization @using EPiServer.Shell @using EPiServer.Shell.Web.Mvc @using EPiServer.Shell.Web.Mvc.Html @using EPiServer.Shell.UI.Web.Mvc.Html @using EPiServer.Shell.Navigation @using EPiServer.Shell.UI.Web.Resources @using EPiServer.Shell.Web; @{ Layout = Layout = "~/Views/Shared/Layouts/_Admin_Root.cshtml"; } @section HeaderContent{ @Html.Raw(ClientResources.RenderResources("Navigation", new[] { ClientResourceType.Style })) @Html.Raw(ClientResources.RenderResources("BootstrapperHeader")) } @if (Model.GlobalNavigationMenuType == GlobalNavigationMenuType.PullDown) { <div id="globalMenuContainer" class="epi-navigation-container epi-navigationContainer" data-dojo-type="epi/shell/widget/GlobalMenu"> @Html.Raw(Html.GlobalMenu()) </div> } @if (Model.UseContainers) { <div data-dojo-type="dijit/layout/BorderContainer" id="rootContainer" gutters="false" style="padding: 0;height: 100%; width: 100%;"> @if (Model.GlobalNavigationMenuType == GlobalNavigationMenuType.Static) { <div data-dojo-type="dijit/layout/ContentPane" class="epi-navigationContainer" region="top" style="border:0; overflow:visible; z-index:900"> @* z-index to make menu drop-downs appear above view content *@ @Html.Raw(Html.GlobalMenu()) </div> } <div data-dojo-type="epi/shell/widget/LicenseInformation" region="top"></div> <div id="applicationContainer" class="epi-applicationContainer" data-dojo-type="epi/shell/widget/Application" region="center"></div> </div> } else { if (Model.GlobalNavigationMenuType == GlobalNavigationMenuType.Static) { <div class="epi-navigationContainer"> @Html.Raw(Html.GlobalMenu()) </div> } <div id="applicationContainer" class="epi-applicationContainer" data-dojo-type="epi/shell/widget/Application" style="height: 100%"> <div></div> </div> } @RenderBody() @section ScriptContent{ @Html.Raw(ClientResources.RenderResources("navigation", new[] { ClientResourceType.Script })) <script type="text/javascript"> var useContainers = @Model.UseContainers.ToString().ToLower(); require(["epi/shell/Bootstrapper"], function (Bootstrapper) { var settings = @Html.Raw(Html.SerializeObject(Model.Modules, KnownContentTypes.Json).Replace("<", "<").Replace(">", ">")), viewSettings = @Html.Raw(Html.SerializeObject(Model.ViewSettings, KnownContentTypes.Json).Replace("<", "<").Replace(">", ">")), bootStrapper = new Bootstrapper(settings); bootStrapper.initializeApplication("@Model.ViewName", "@Model.ModuleName", viewSettings).then(function () { require([ "dojo/_base/connect", "dojo/parser", "dijit/registry", "dijit/layout/BorderContainer", "dijit/layout/ContentPane", "epi/shell/widget/GlobalMenu", "epi/shell/widget/Application", "epi/shell/widget/LicenseInformation", "epi/shell/widget/ApplicationContentPane", "dojo/domReady!"], function (connect, parser, registry) { parser.parse(); if (useContainers) { // Trigger layout when the global navigation changes its layout. connect.subscribe("/epi/shell/globalnavigation/layoutchange", null, function() { registry.byId("rootContainer").layout(); }); } }); }); }); </script> }
I still got the issue that i get the error:
Uncaught ReferenceError: epi is not defined
And i now got a new error i dont quite know what to make of:
dojo/parser::parse() error Error: Could not resolve dependency "epi.storeregistry" (epi.dependency) at _1d.declare.resolve (http://epi.sandbox/EPiServer/Shell/8.2.2.0/clientresources/epi/epi.js:2:1744) at _1.postMixInProperties (http://epi.sandbox/EPiServer/Shell/8.2.2.0/ClientResources/EPi/shell/widget/LicenseInformation.js:2:1297) at _6.create (http://epi.sandbox/EPiServer/Shell/8.2.2.0/clientresources/dijit/_WidgetBase.js:2:1602) at _6.postscript (http://epi.sandbox/EPiServer/Shell/8.2.2.0/clientresources/dijit/_WidgetBase.js:2:1349) at new <anonymous> (http://epi.sandbox/EPiServer/Shell/8.2.2.0/clientresources/dojo/dojo.js:15:41502) at Object._1b.construct (http://epi.sandbox/EPiServer/Shell/8.2.2.0/clientresources/dojo/parser.js:2:5398) at Object.<anonymous> (http://epi.sandbox/EPiServer/Shell/8.2.2.0/clientresources/dojo/parser.js:2:1948) at Object._172.map (http://epi.sandbox/EPiServer/Shell/8.2.2.0/clientresources/dojo/dojo.js:15:33693) at Object._1b._instantiate (http://epi.sandbox/EPiServer/Shell/8.2.2.0/clientresources/dojo/parser.js:2:1794) at http://epi.sandbox/EPiServer/Shell/8.2.2.0/clientresources/dojo/parser.js:2:8935
Anyone know what im missing, i cant seam to be able to get rid of the "epi is not defined" i have tried move things around but this is the order that stuff are orderd and loaded in epi's built in sleek.master so it should be right.
You shouldn't need to copy the sleek.master or bootstrapper page. Just use the one bundled in EPiServer. Your controller should look somthing like this:
[MenuSection("/global/Aw", Text = "Adventure Works", SortIndex = 10000)] [Authorize(Roles = "CmsEditors, CmsAdmins")] public class CustomController : Controller { private readonly IBootstrapper _bootstrapper; public CustomController(IBootstrapper bootstrapper) { _bootstrapper = bootstrapper; } [MenuItem("/global/Aw/Person", Text = "Persons")] public ActionResult Index() { var viewModel = _bootstrapper.CreateViewModel("/adventureworks/person", ControllerContext, "Shell"); viewModel.GlobalNavigationMenuType = GlobalNavigationMenuType.PullDown; return View(_bootstrapper.BootstrapperViewName, viewModel); } }
Then all you need is a composite view whose name property matches the name given in the view model, i.e. "/adventureworks/person". Something like this:
[CompositeView] public class CustomView : ICompositeView { private IContainer _rootContainer; public IContainer RootContainer { get { if (_rootContainer == null) { var navigation = new PinnablePane().Add(new ComponentPaneContainer { PlugInArea = "/custom/navigation", ContainerType = ContainerType.User }); var tools = new PinnablePane().Add(new ComponentPaneContainer { PlugInArea = "/custom/assets", ContainerType = ContainerType.User }); var content = new BorderContainer() .Add(new ContentPane { PlugInArea = "/episerver/cms/action" }, new BorderSettingsDictionary(BorderContainerRegion.Top)) .Add(new ContentPane { PlugInArea = "/episerver/cms/maincontent" }, new BorderSettingsDictionary(BorderContainerRegion.Center)); _rootContainer = new BorderContainer() .Add(navigation, new BorderSettingsDictionary(BorderContainerRegion.Leading, new Setting("minSize", 260), new Setting("splitter", "true"), new Setting("liveSplitters", "false"), new Setting("id", "navigation"))) .Add(content, new BorderSettingsDictionary(BorderContainerRegion.Center)) .Add(tools, new BorderSettingsDictionary(BorderContainerRegion.Trailing, 400, 300, null, new Setting("splitter", "true"), new Setting("liveSplitters", "false"), new Setting("id", "tools"))); _rootContainer.Settings["id"] = Name + "_rootContainer"; } return _rootContainer; } } public string Name { get { return "/adventureworks/person"; } } public ICompositeView CreateView() { return new CustomView(); } public string Title { get { return "Custom View"; } } public string DefaultContext { get { return SiteDefinition.Current.SiteAssetsRoot.GetUri().ToString(); } } }
The example above will create a list view of the content in the site assets root. This is because there are components configured to automatically plug themselves in to the plug in areas in the example. If you change those areas then you can create your own components to have automatically pluged in or create a component class and add it to the container in the code above.
You also should not need to add you module to the protected modules in the web.config.
Hope this helps.
Thank you ben that was an eye opener, i now got no errors and have continued on to try creating a components to view stuff. I made on compnent:
using EPiServer.Shell.ViewComposition; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Epi8Sandbox.Components { [Component( PlugInAreas = "/Aw/person/MainContent", Categories = "Aw", WidgetType = "aw.components.mainContent", Title = "AdventureWorks Person Content", Description = "My AdventureWorks main content area", IsAvailableForUserSelection = false)] public class PersonComponent : ContainerBase { public PersonComponent(): this(null) { } public PersonComponent(params Setting[] settings) :this(null, false, false, false, false, false, null, null, settings) { } public PersonComponent(string href, bool extractContent, bool parseOnLoad, bool preventCache, bool preload, bool refreshOnShow, string loadingMessage, string errorMessage, params Setting[] settings) : base("dijit.layout.ContentPane") { if(settings == null) return; if (href != null) Settings["href"] = href; Settings["parseOnLoad"] = parseOnLoad; Settings["preventCache"] = preventCache; Settings["preload"] = preload; Settings["refreshOnShow"] = refreshOnShow; if (loadingMessage != null) Settings["loadingMessage"] = loadingMessage; if (errorMessage != null) Settings["errorMessage"] = errorMessage; Settings.MergeRange(settings); } } }
And created a mainContent.js located below ~/Clientresources/Scripts/Widgets/components/mainContent.js
define("aw.components.mainContent", [ "dojo/_base/declare", "dijit/_WidgetBase", "dijit/_TemplatedMixin" ], function ( declare, _WidgetBase, _TemplatedMixin ) { return declare("aw.components.mainContent", [_WidgetBase, _TemplatedMixin], { templateString: '<div>Some hard-coded HTML.</div>' }); });
The location is based on the modules registration i read this here. And my modules.config
<?xml version="1.0" encoding="utf-8"?> <module> <assemblies> <!-- This adds the Alloy template assembly to the "default module" --> <add assembly="Epi8Sandbox" /> </assemblies> <clientResources> <add name="epi-cms.widgets.base" path="Styles/Styles.css" resourceType="Style"/> </clientResources> <dojo> <!-- Add a mapping from alloy to ~/ClientResources/Scripts to the dojo loader configuration --> <paths> <add name="alloy" path="Scripts" /> <add name="aw" path="Scripts/Widgets" /> </paths> </dojo> </module>
However my script file is never loaded and i dont guite understand why. And i would really want to route it to a controller to display a view if at all possible.
One other thing is, in my composite view i got this:
var content = new BorderContainer() .Add(new ContentPane { PlugInArea = "/episerver/cms/action" }, new BorderSettingsDictionary(BorderContainerRegion.Top)) .Add(new PersonComponent(), new BorderSettingsDictionary(BorderContainerRegion.Center));
and it gets me a buttonbar at the top of the page with a some buttons one the most left and right might be my pinnable panes, but i got buttons in the middle that i do not want also, and i dont quite understand how to override it. If i comment out the content pane with PlugInArea "/episerver/cms/action" i get nothing at al.
The string that you pass though to the base class contructor on your component should be the path of your JavaScript component, i.e. "aw.components.mainContent"
Although kind of lousy, there is some documentation here http://world.episerver.com/documentation/Items/Developers-Guide/EPiServer-CMS/8/User-interface/Views/Views/
I did this and it now serves the view from my controller.
.Add(new PersonComponent("Person/Test", false, false, false, false, false, "Loading epic shit", "Epic shit failed"), new BorderSettingsDictionary(BorderContainerRegion.Center));
Now to actually make some real components to dock in the pinnable and override the "/episerver/cms/action" pane with only one pinnable and my custom widgets in there.
And as you said their documentation is really shitty for these things.
Have realized i dont need to create a component the ContentPane does exacly what my PersonComonent did. Im trying to find a implementation of the "new ContentPane { PlugInArea = "/episerver/cms/action" }" but cannot find this in any js module or any code with reflection so i dont understand how to create this with only a pinnable leading button.
Although this actually works to display a view from my controller
new ContentPane("Person/Test", false, false, false, false, false, "Loading epic shit", "Epic shit failed")
But it gets really messed up with a lot of wrapping div's and a lot of dojo/dijit shit, i would like to clean this up and i would like to include my own css/js stuff somehow either to the bootstrap page somehow or get the ContentPane to load my content within a iframe so i can use a layout with my own styles and frameworks.
I somehow wants to create a component/widget or something to display a navigation in the leading pinnable panel (so i can use the entire page for content if i wish) and when clicked reload the main content from my controllers and views. But i feel very lost due to the lousy documentations.
Hi Mattias
If you are looking at creating an iframe based component then Linus has an great post about this: http://world.episerver.com/blogs/Linus-Ekstrom/Dates/2012/10/Extending-the-User-Interface-of-EPiServer-7/
I have also put a post together that collects some useful posts together around dojo: http://www.david-tec.com/2014/08/EPiServer-Dojo-Useful-links/
I hope it helps you in your quest! I am interested in the outcome :)
David
Thanks david, I have looked at that post by linus before but since it is an attribute used on webforms i have not payed to much interest in it, also the documentation refers to the same thing and yet again it only covers webforms so does that even work for mvc ?
I will go through your links and se if there is something i can make use of.
Thanks to the links on you collection of dojo stuff i managed to create some basic/static html widgets that both serve a static html file and a mvc view. I have found the GlobalToolbar and begun to copy it somewhat.
GlobalToolbar.js
define("aw/person/GlobalToolbar", [ // Dojo "dojo/_base/declare", "dojo/_base/lang", "dojo/dom-style", "dojo/store/Memory", // Dijit "dijit/registry", "dijit/Toolbar", // EPi CMS "epi/shell/command/_CommandModelBindingMixin", "epi/shell/command/_WidgetCommandConsumerMixin", "epi/shell/command/builder/_Builder", "epi/shell/command/builder/GroupedButtonBuilder", "epi/shell/command/builder/MenuAssembler", "epi/shell/command/builder/DropDownButtonBuilder", "epi/shell/command/builder/ExpandoMenuBuilder", //Template "dojo/text!./GlobalToolbar.html" ], function ( // Dojo declare, lang, domStyle, Memory, // Dijit registry, Toolbar, // EPi CMS _CommandModelBindingMixin, _WidgetCommandConsumerMixin, _Builder, GroupedButtonBuilder, MenuAssembler, DropDownButtonBuilder, ExpandoMenuBuilder, template ) { return declare([Toolbar, _WidgetCommandConsumerMixin], { // summary: // A component providing the global toolbar. // // tags: // internal templateString: template, commandKey: "aw.components.GlobalToolbar", }); });
And i have modified my composite view to add this global toolbar:
var content = new BorderContainer() .Add(new ContentPane { PlugInArea = "aw/person/GlobalToolbar" }, new BorderSettingsDictionary(BorderContainerRegion.Top)) .Add(new ContentPane("Person/Test", false, false, false, false, false, "Loading epic shit", "Epic shit failed"), new BorderSettingsDictionary(BorderContainerRegion.Center));
But it never loads the js file, however if i would create a component it loads the js file but nothing reaches the screan. My second content pane wich loads my mvc view for "Person/Test" only loads when i use "/episerver/cms/action" for PlugInArea for the first content pane. I dont quite understand how to define my global toolbar to make it register with my content pane who uses PlugInArea = "aw/person/GlobalToolbar".
Im playing around with a Epi 8 project trying to learn more about extending the global navigation with custom controllers and views.
Im using the Adventure Works 2014 sample database, to display sample data to manipulate.
I Have created a controller with MenuSecton & MenuItem attributes.
This gave me a new menu section and a menu item, that was simple enough. So i dug out the sleek.master and bootstrap.aspx, and reverse enginered them to mvc.
And this gave me these two layouts.
#1, _Admin_Root.cshtml:
#2, _Admin_Bootstrap.cshtml
This is where i get really stuck. I looked up the dashboar dcontroller and it seams to use the BootstrapperViewModel so therefore i used that as model in my layouts.
And if i create a new model and set some values like this for example:
I get this error, in my view:
The given key was not present in the dictionary.
So by some diging and looking at the dashboard controller i did this:
Now the view loads but i get the dashboard view straight of epi. But i get a console error:
Uncaught ReferenceError: epi is not defined
Looks like it is the generated script from this line just after the body tag, that is generating the error:
What i want is a layout that has the global navigation where i can use epi shell ux styles to get a epi feel of my views.
I would also like to be able to get the top bar that you get in the cms edit view (gray area on top of pages with buttons),
and it would be nice to be able to put menus or content in the pinnable areas (content tree, and media/block area).
I dont really want all the dojo dijit stuff but if i remove them in the botom of my bootstrap layout the global nav get totaly white
and the background, also the global navigation gets static and not able to fold it up anymore.