Skip to content

Commit

Permalink
Merge pull request #3 from collective/global-action
Browse files Browse the repository at this point in the history
Added option to show action globally, regardless of blocked portlets.
  • Loading branch information
mauritsvanrees committed Nov 1, 2016
2 parents 124c60c + f2ccd85 commit fdb7f52
Show file tree
Hide file tree
Showing 15 changed files with 250 additions and 64 deletions.
4 changes: 3 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ Changelog
1.0rc2 (unreleased)
-------------------

- Nothing changed yet.
- Added option to show action globally, regardless of blocked portlets.
The timeout is now always the time since the first visit of a page with this portlet.
[maurits]


1.0rc1 (2016-04-20)
Expand Down
10 changes: 7 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ or button.
Compatibility
-------------

Works on Plone 4.3.x, tested explicitly on Plone 4.3.8.
Works on Plone 4.3.x, tested explicitly on Plone 4.3.8 and 4.3.11.
Not yet compatible with Plone 5: the javascript and css need work.


Expand All @@ -29,9 +29,10 @@ Features
This is basically a copy of the static text portlet with a few extra options.

- In the portlet you can set the number of milliseconds before the overlay is shown.
This can be several minutes and go over multiple pages:
using a cookie, we keep track of how long you have been on the site.

- When the overlay is shown, a cookie is set.
We use this to show the overlay only once.
- When the overlay is shown, the cookie is updated so that we show the overlay only once.
The cookie is specific for this portlet:
a new call to action portlet will be shown once too.

Expand All @@ -47,6 +48,9 @@ Features
but only one overlay is shown on a page.
If there are three portlets, and the user has already seen the first one but not the others, then the second one will be shown.

- There is a control panel where you can say that the action is global across the site.
This can help if parts of your site block the portlets and you still want to see the action there.


Examples
--------
Expand Down
1 change: 1 addition & 0 deletions src/collective/calltoaction/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@

from zope.i18nmessageid import MessageFactory


_ = MessageFactory('collective.calltoaction')
7 changes: 7 additions & 0 deletions src/collective/calltoaction/browser/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,11 @@
permission="zope2.View"
/>

<browser:page
name="calltoaction-controlpanel"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
permission="cmf.ManagePortal"
class=".controlpanel.CalltoactionControlPanelView"
/>

</configure>
16 changes: 16 additions & 0 deletions src/collective/calltoaction/browser/controlpanel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from collective.calltoaction import _
from collective.calltoaction.interfaces import ICollectiveCalltoactionSettings
from plone.app.registry.browser.controlpanel import ControlPanelFormWrapper
from plone.app.registry.browser.controlpanel import RegistryEditForm
from plone.z3cform import layout
from z3c.form import form


class CalltoactionControlPanelForm(RegistryEditForm):
form.extends(RegistryEditForm)
schema = ICollectiveCalltoactionSettings

CalltoactionControlPanelView = layout.wrap_form(
CalltoactionControlPanelForm, ControlPanelFormWrapper)
CalltoactionControlPanelView.label = _(u'Call to action settings')
24 changes: 21 additions & 3 deletions src/collective/calltoaction/browser/viewlet.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# -*- coding: utf-8 -*-
from collective.calltoaction.interfaces import ICollectiveCalltoactionSettings
from collective.calltoaction.portlets.calltoactionportlet import ICallToActionPortlet # noqa
from plone import api
from plone.app.layout.viewlets.common import ViewletBase
from plone.portlets.interfaces import IPortletManager
from plone.portlets.interfaces import IPortletRenderer
from plone.portlets.interfaces import IPortletRetriever
from plone.registry.interfaces import IRegistry
from Products.Five import BrowserView
from zope.component import getMultiAdapter
from zope.component import getUtility

Expand All @@ -16,11 +20,18 @@ def update(self):
# footer = getUtility(IPortletManager, name='plone.footerportlets')
# But portlets in Plone 5 need to be based on z3c.form, so it may be
# tricky to support Plone 4 and 5 with the same code base.

registry = getUtility(IRegistry)
settings = registry.forInterface(
ICollectiveCalltoactionSettings, check=False)
self.show_global = settings.show_global
if self.show_global:
target = api.portal.get_navigation_root(self.context)
else:
target = self.context
for name in ('plone.leftcolumn', 'plone.rightcolumn'):
manager = getUtility(IPortletManager, name=name)
retriever = getMultiAdapter(
(self.context, manager), IPortletRetriever)
(target, manager), IPortletRetriever)
portlets = retriever.getPortlets()
for portlet in portlets:
assignment = portlet['assignment']
Expand Down Expand Up @@ -48,5 +59,12 @@ def _data_to_portlet(self, manager, data):
Adapted from plone.portlets/manager.py _dataToPortlet.
"""
return getMultiAdapter((self.context, self.request, self.view,
if self.show_global:
target = api.portal.get_navigation_root(self.context)
# Use dummy view for the target context.
view = BrowserView(target, self.request)
else:
target = self.context
view = self.view
return getMultiAdapter((target, self.request, view,
manager, data, ), IPortletRenderer)
8 changes: 8 additions & 0 deletions src/collective/calltoaction/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,12 @@
provides="Products.GenericSetup.interfaces.EXTENSION"
/>

<genericsetup:upgradeDepends
source="1000"
destination="1001"
title="Add registry settings and controlpanel"
profile="collective.calltoaction:default"
import_steps="plone.app.registry controlpanel"
/>

</configure>
19 changes: 19 additions & 0 deletions src/collective/calltoaction/interfaces.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
# -*- coding: utf-8 -*-
"""Module where all interfaces, events and exceptions live."""

from collective.calltoaction import _
from zope import schema
from zope.interface import Interface
from zope.publisher.interfaces.browser import IDefaultBrowserLayer


class ICollectiveCalltoactionLayer(IDefaultBrowserLayer):
"""Marker interface that defines a browser layer."""


class ICollectiveCalltoactionSettings(Interface):
show_global = schema.Bool(
title=_(u'Show global action'),
description=_(
u'description_global_action',
default=(
u'This looks for a portlet in the navigation root, '
u'which usually is the portal root. '
u'It shows it on every page. '
u'The timeout before showing the call to action, '
u'is calculated from the first page visit. '
u'The time spent on the site so far is stored on a cookie.')),
default=False,
)
20 changes: 20 additions & 0 deletions src/collective/calltoaction/profiles/default/controlpanel.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<object
name="portal_controlpanel"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="collective.calltoaction">

<configlet
title="Call to action settings"
action_id="collective.calltoaction.settings"
appId="collective.calltoaction"
category="Products"
condition_expr=""
url_expr="string:${portal_url}/@@calltoaction-controlpanel"
icon_expr="string:${portal_url}/product_icon.png"
visible="True"
i18n:attributes="title">
<permission>Manage portal</permission>
</configlet>

</object>
2 changes: 1 addition & 1 deletion src/collective/calltoaction/profiles/default/metadata.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<version>1000</version>
<version>1001</version>
<dependencies>
</dependencies>
</metadata>
3 changes: 3 additions & 0 deletions src/collective/calltoaction/profiles/default/registry.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<registry>
<records interface="collective.calltoaction.interfaces.ICollectiveCalltoactionSettings" />
</registry>
7 changes: 7 additions & 0 deletions src/collective/calltoaction/profiles/testfixture/portlets.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,11 @@
<property name="milli_seconds_until_overlay">1000</property>
</assignment>

<blacklist
manager="plone.rightcolumn"
category="context"
location="/folder2"
status="block"
/>

</portlets>
149 changes: 95 additions & 54 deletions src/collective/calltoaction/resources/calltoaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,68 +3,109 @@
$(document).ready(function() {
var overlay_set = false;
$('.calltoaction-portlet-wrapper').each( function() {
var time_on_site;
var cookie_value;
var el;
var cookiename;
var start;
var timeout;
if (overlay_set) {
return;
}
// Check if the user has already seen this overlay.
var cookiename = $(this).attr('data-cookiename');
cookiename = $(this).attr('data-cookiename');
// Note: readCookie and createCookie are defined in
// Products/CMFPlone/skins/plone_ecmascript/cookie_functions.js
if (!overlay_set && !readCookie(cookiename)) {
var timeout = $(this).attr('data-timeout');
var el = $(this);
setTimeout(
function(){
// Overlay adapted from http://jquerytools.github.io/demos/overlay/trigger.html
el.overlay({
// custom top position
top: "center",
fixed: true,
// Before the overlay is gone be active place it correctly
onBeforeLoad: function() {
cookie_value = readCookie(cookiename);
if (cookie_value === 'y') {
// already seen
return;
}
timeout = parseInt($(this).attr('data-timeout'));
if (isNaN(timeout)) {
timeout = 5000;
}
cookie_value = parseInt(cookie_value);
start = cookie_value;
if (isNaN(start)) {
start = new Date().getTime();
} else {
/*
Say start is 12:00 (but then in milliseconds).
And current time is 12:02.
And timeout is 3 minutes.
Then the new timeout is 1 minute.
*/
time_on_site = new Date().getTime() - start;
if (time_on_site > 3600000) {
// Reset time on site after an hour.
start = new Date().getTime();
time_on_site = 0;
}
timeout = timeout - time_on_site;
}
if (cookie_value !== start) {
// Remember the session start time in a cookie.
createCookie(cookiename, start, 365);
}
el = $(this);
setTimeout(
function(){
// Overlay adapted from http://jquerytools.github.io/demos/overlay/trigger.html
el.overlay({
// custom top position
top: "center",
fixed: true,
// Before the overlay is gone be active place it correctly
onBeforeLoad: function() {

if (el.hasClass("manager_right")){
el.animate({right: -1000});
}else{
el.animate({left: -1000});
};
if (el.hasClass("manager_right")){
el.animate({right: -1000});
}else{
el.animate({left: -1000});
}

},
// when the overlay is opened, animate our portlet
onLoad: function() {
},
// when the overlay is opened, animate our portlet
onLoad: function() {

if (el.hasClass("manager_right")){
el.animate({right: 15});
}
else {
el.animate({left: 15});
};
if (el.hasClass("manager_right")){
el.animate({right: 15});
}
else {
el.animate({left: 15});
}

},
// some mask tweaks suitable for facebox-looking dialogs
mask: {
// you might also consider a "transparent" color for the mask
color: '#fff',
// load mask a little faster
loadSpeed: 200,
// very transparent
opacity: 0.5
},
// disable this for modal dialog-type of overlays
closeOnClick: true,
// load it immediately after the construction
load: true,
},
// some mask tweaks suitable for facebox-looking dialogs
mask: {
// you might also consider a "transparent" color for the mask
color: '#fff',
// load mask a little faster
loadSpeed: 200,
// very transparent
opacity: 0.5
},
// disable this for modal dialog-type of overlays
closeOnClick: true,
// load it immediately after the construction
load: true

});

// Set cookie to avoid showing overlay twice to the same
// user. We could do this on certain events, but you have
// to catch them all: onClose of the overlay, clicking on
// a link in the overlay, etcetera. Much easier to simply
// set the cookie at the moment we show the overlay.
createCookie(cookiename, 'y', 365);
},
timeout);
// We setup only one overlay, otherwise it gets a bit crazy.
overlay_set = true;
};
});

/*
Set cookie to avoid showing overlay twice to the same
user. We could do this on certain events, but you have
to catch them all: onClose of the overlay, clicking on
a link in the overlay, etcetera. Much easier to simply
set the cookie at the moment we show the overlay.
The 'y' value means: yes, the user has seen it.
*/
createCookie(cookiename, 'y', 365);
},
timeout);
// We setup only one overlay, otherwise it gets a bit crazy.
overlay_set = true;
});
});
})(jQuery);

0 comments on commit fdb7f52

Please sign in to comment.