Adding viewlets into fixed, specific locations in viewlet manager - plone

NOTE
Due to the successful outcome of this thread, the documentation at the provided link has been updated. It no longer contains the incomplete information as described.
I'm having an issue taking viewlets that appear in the plone.portalheader viewletManager and adding them to specific locations (not utilizing the linear concatenation). My goal is to make the html code look identical to my html template mockup. I do not want nor require the viewlets to change positions and I would like to see a couple of those elements wind up in the same overarching markup.
I have been reading the following documentation, but it seems to be in conflict with my set up (which is Plone 4.1):
http://collective-docs.readthedocs.org/en/latest/views/viewlets.html#rendering-viewlets-with-accurate-layout
I'm not sure if that particular page is outdated or incorrect (it looks like most references to "tab" were replaced with spaces. eg, <table> is now < le>) or if I'm doing something wrong (which I wouldn't put it past me).
It looks like when I want to call the new header created from the example, citing something.header fails inside my portal_header.pt file. If someone can eyeball the example and let me know if there are any important parts missing, that would be a huge help to me.
Including my code:
theme/browser/configure.zcml (portion):
<!-- The portal header -->
<browser:viewlet
name="plone.header"
manager="plone.app.layout.viewlets.interfaces.IPortalTop"
layer=".interfaces.IThemeSpecific"
class=".header.HeaderViewlet"
template="templates/portal_header.pt"
permission="zope2.View"
/>
theme/browser/templates/portal_header.pt:
<header>
<div class="container_12">
<div tal:replace="structure provider:theme.header" />
</div>
</header>
theme/browser/header.py:
from Acquisition import aq_inner
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from plone.app.layout.viewlets import common as base
#import plonetheme.something.browser.viewlets.common as something #left over from original article
def render_viewlet(factory, context, request):
context = aq_inner(context)
viewlet = factory(context, request, None, None).__of__(context)
viewlet.update()
return viewlet.render()
class HeaderViewlet(base.ViewletBase):
index = ViewPageTemplateFile('header_items.pt')
def update(self):
base.ViewletBase.update(self)
self.subviewlets = {}
def renderViewlet(self, viewlet_class):
return render_viewlet(viewlet_class, self.context, self.request)
def render(self):
self.subviewlets["logo"] = self.renderViewlet(base.LogoViewlet)
self.subviewlets["search"] = self.renderViewlet(base.SearchBoxViewlet)
return self.index()
theme/browser/header_items.pt:
<header>
<div class="container_12">
<div tal:replace="structure view/subviewlets/logo" />
<div tal:replace="structure view/subviewlets/search" />
</div>
</header
That's all I have modified per the article I listed above. I'm unsure if I should modify viewlets.xml.
From what I understand of the article, it wants to override and replace the plone.portalheader in portal_header.pt with theme.header by utilizing the class provided in header.py. However, I'm not modifying viewlets.xml which is where I believe the problem lies. Or it could like with the fact that I'm replacing plone.portalheader with theme.header and I'm not changing the name of the viewlet in configure.zcml from plone.header to theme.header.
I've tried a whole bunch of permutations on what I believe to be the solution and I can't get anything to work, even trying to modify viewlets.xml.
When I follow the example I get an error message that says:
ContentProviderLookupError(u'theme.header',)

TAL provider: expression will only render portlet managers or viewlet managers. It does not work with viewlets. That's why there is render_viewlet(factory, context, request) shortcut in the example.
What you want to is to
create a viewlet which contains all of your layout (done, HeaderViewlet)
place this viewlet in good known viewlet manager (use /##manage-viewlet to figure out which) - this is not done
then in this new viewlet render subviewlets as you do
If you need to create a new viewlet manager (ugh, pain in the ass and shitty job) you can follow instructions here:
http://opensourcehacker.com/2011/11/29/creating-a-viewlet-manager-in-plone/

Related

Data-Link to CSS background-image using a helper function not render

I have a template which renders a background image to CSS within a <div> like this
style="background-image: url({{>~facilityImagePathDisplay(#data)}})"
Visual Studio doesn't like this and it's not a big deal but I wanted to see if there was a proper way to do this. I read through the documentation. Other stack questions and the below was created based off of the docs. This specifically links to all the docs Logic in JsViews css-tag
<div class="tile-image" data-link="css-background-image{:~facilityImagePathDisplay(#data)}" >
The above statement outputs exactly this to the div. <div class="tile-image" data-link="css-background-image{:~facilityImagePathDisplay(#data)}"> so it looks like it's not firing the helper function and rendering anything at all. Am I missing something?
It depends if you are running the link() method of JsViews, or simply the render() method of JsRender.
Your first version will works in both cases, but is simply rendering the CSS style.
The second version would only be used if you are running the link() method of JsViews - for data-binding etc. (So the background image could be dynamically updated if you data-binding triggers a new value).
For the data-link version, you need to makes sure the helper is returning a string that is not just "someUrl", but "url(someUrl)" - to provide the correct CSS data url syntax. In that case, the following should work:
data-link="css-background-image{:~facilityImagePathDisplay(#data)}"
Alternatively you can have the helper return just the "someUrl" string, but then include the "url(" and ")" strings in the template:
data-link="css-background-image{:'url(' + ~facilityImagePathDisplay(#data) + ')'}"

How to move components around

I'm using the 0.4.0 branch for the components as HTML files functionality. I'm trying to do the following: I have a component that controls the layout of a page. This component has some subcomponents as an array and displays them on different parts of the page based on some data in the subcomponent. Something akin to this (due to layout restrictions they have to be in different parts of the page):
<div id="section1">
<h1> Section 1 </h1>
{{# subcomponents}}
{{#isflagsection1(flag)}}
<subcomponent flag={{flag}}/>
{{/isflag}}
{{/subcomponents}}
</div>
<div id="section2">
<h1> Section 2 </h1>
{{# subcomponents}}
{{#isflagsection2(flag)}}
<subcomponent flag={{flag}}/>
{{/isflag}}
{{/subcomponents}}
</div>
<div id="section3">
<h1> Section 3 </h1>
{{# subcomponents}}
{{#isflagsection3(flag)}}
<subcomponent flag={{flag}}/>
{{/isflag}}
{{/subcomponents}}
</div>
The flag is updated from controls within each component. this works great (the DOM is refreshed each time I modify the flag) except for one issue. Instead of performing a move, the subcomponent is recreated every time the flag changes, e.g. it's destroyed and created a new. This is unfortunate for my use case because of two reasons:
The subcomponent has a rather heavy creation cost (specially in mobile) since it performs some graphics work.
The subcomponent stores some private data (a
history of changes made to the model) that either a) gets lost when
it's moved along to another section or b) has to be stored in the
top component polluting it's data model.
So what I would like to know is, is there a way to "move" the component without deleting/recreating it?
Regards,
V. SeguĂ­
Yes - every Ractive instance has two methods that allow you to do this: ractive.detach() and ractive.insert(). Unfortunately the documentation is currently lacking, but here's how you use it:
// remove the instance from the DOM, and store a document
// fragment with the contents
docFrag = ractive.detach();
// insert the instance into the container element, immediately
// before nodeToInsertBefore (the second argument is optional -
// if absent or `null` it means 'insert at end of container'
ractive.insert( container, nodeToInsertBefore );
If you're removing the instance and immediately reinserting it, there's no need to detach it first - you can just do ractive.insert(). The arguments can be DOM nodes, but they can also be CSS selectors or the IDs of elements.
Here'a a JSFiddle demonstrating: http://jsfiddle.net/rich_harris/Uv8WJ/
You can also do this with inline components (i.e. <subcomponent/> as opposed to new Subcomponent(). In this JSFiddle, we're using ractive.findComponent('subcomponent') method to get a reference to the instance: http://jsfiddle.net/rich_harris/f28t5/.

Load html (getText) based on global tabs

I am trying to load different html pages based on each global tab (each tab would load a different html page) using getText. Does anyone know the code to specify a certain tab for each different piece of text? The code below loads the same text into all the tabs:
<div class="col_3" metal:define-macro="highlights" i18n:domain="plone">
<h2>Highlights</h2>
<p>
<tal:block tal:condition= "exists:here/graduate-study/highlghts-grad"
tal:replace="structure here/graduate-study/highlghts-grad/getText">Footer content here</tal:block>
</p>
</div>
<div class="col_3" metal:define-macro="highlights" i18n:domain="plone">
<h2>Highlights</h2>
<p>
<tal:block tal:condition= "exists:here/undergraduate-study/highlghts-grad"
tal:replace="structure here/undergraduate-study/highlghts-grad/getText">Footer content here</tal:block>
</p>
</div>
etc, etc for all tabs...
The global tabs in Plone are navigation roots. You can detect the current active navigation root in two ways:
The body HTML tag has a section-[id_of_section] CSS class, where id_of_section varies with the pathname of each section.
From a viewlet or portlet you can retrieve these classes using the ##plone_layout view:
tal:define="plone_layout context/##plone_layout;
bodyClass python:plone_layout.bodyClass(template, view)"
after which you'll have to test if a certain state- string is present in the bodyClass value.
By retrieving he current navigation root directly from the ##plone_portal_state view:
tal:define="plone_portal_state context/##plone_portal_state;
nav_root plone_portal_state/navigation_root;
nav_root_id nav_root/getId"
You can then vary your viewlet or portlet based on the nav_root_id.
Note however that each navigation root is has a portal type, and possibly an interface declaration, that you could use to register viewlets or portlets for. Detecting the exact navigation is usually not the best option.
This effect is commonly referred to as a "megamenu." There is a product that implements this for Plone, http://plone.org/products/collective.collage.megamenu/. I've not used it, but I'd certainly recommend that you explore it as a starting point.

What are the rules plone works with to decide which menus to display, and when?

I'm using Plone 4.0.5, and I've spent the day trying to understand plone.app.contentmenu.
I have a custom folder based archetypes type, and I've written a view for it. When using the default base_view (site/MyObject/base_view), everything works as expected. Using my own custom view though, the menus start disappearing, and I haven't been able to figure it out.
First, my zcml,
<browser:page
for="my.product.interfaces.IMyType"
name="view"
class="my.product.browser.mytype.MyTypeViewView"
permission="zope.Public"
/>
The view itself is as simple as possible:
class MyTypeViewView(BrowserView):
template = ViewPageTemplateFile("templates/mytype_view.pt")
def __call__(self):
return self.template()
And the view template has also been slimmed down to nothing:
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
lang="en"
metal:use-macro="context/main_template/macros/master"
i18n:domain="plone">
<body>
<metal:content-core fill-slot="content-core">
hi there.
</metal:content-core>
</body>
</html>
If I access site/MyObject/view, the template shows "hi there", but plone-contentmenu-actions and plone-contentmenu-workflow disappear. plone-contentmenu-factories however, remains. if I then modify the ZCML to set name="view_test", and visit site/MyObject/view_test, none of the menus at all display.
I have five different views for this type, and I want the workflow menu to display on all of them (or at the very least, my main view, so that I can test more easily until I figure it out).
If I rename my view to base_view and visit the object URL directly, I still don't have a workflow menu available.
I guess my question should be:
What exactly are the rules plone works with to decide which menus to display, and when? What code should I be reading?
-- edit:
I've added this function to my View:
def __init__(self, context, request):
super(MyTypeViewView, self).__init__(context, request)
alsoProvides(self, IViewView)
alsoProvides(self.context, IViewView)
if I place a breakpoint in my call code, I get this:
>>> IViewView.providedBy(self)
True
>>> IViewView.providedBy(self.context)
True
I'm pretty sure I only needed to apply IViewView to the view itself, but regardless, this doesn't change anything for me.
Plone applied the IViewView marker interface to the view which are canonical for an object - the one you get clicking the view tab. Certain parts of the interface are restricted to that interface - see the section "Restricting a viewlet to the canonical view" in http://plone.org/products/dexterity/documentation/manual/five.grok/browser-components/viewlets
Since you already have a view Class (MyTypeViewView) for your page it's more straight-forward to make it implement the interface instead of make instances of it provide it:
from plone.app.layout.globals.interfaces import IViewView
class MyTypeViewView(BrowserView):
interface.implements(IViewView)
...
Although I think that this won't solve your problem since I do have lots of custom views that do not implement IViewView but display all actions nicely.
I guess you simply need to add your views to the available default views for your type in portal_types tool and you're fine.

Wrapper for custom Content Part - Orchard CMS

Using the Orchard admin I created a new Content Part called 'Spotlight Wrapper' with 3 HTML fields. I then created a Content Type Called 'Template 1' and assigned 'Spotlight Wrapper' to it. I then created a new 'Template 1' content item called 'Home Page'. I then created a file called Fileds_Contrib.Html-Spotlight Wrapper.cshtml to wrap each HTML field in the 'Spotlight Wrapper' with an and this is working. I have now added:
<Place Parts_SpotlightWrapper="Content:before;Wrapper=Parts_SpotlightWrapper" />
And created :
Views\Parts.SpotlightWrapper.cshtml
in an attempt to wrap the entire 'Spotlight Wrapper' Content Part in a but cannot seem to get it to work?
You declared a wrapper which I guess would lead to circular reference, as you try to wrap the Parts_SpotlightWrapper shape with itself. Wrappers are just separate pieces of Razor (cshtml) code that act as a parent for a given shape.
To achieve the behavior you want you should create a separate .cshtml file (eg. MyWrapper.cshtml) containing the necessary wrapper HTML code and attach it to your existing part like this:
<Place Parts_SpotlightWrapper="Content:before;Wrapper=MyWrapper" />
The wrapper code could look eg. like this:
<ul>
#Display(Model.Child)
</ul>
Btw - Try to look how it's done in Orchard.Widgets. There are two wrappers Widget.Wrapper and Widget.ControlWrapper that wrap the Widget shape. Declarations of those are not inside the Placement.info file (as you did), but hardcoded in Shapes.cs shape definition, though the final effect is perfectly the same. The technique with the Placement.info was just introduced later as a shortcut.
HTH

Resources