(navigate using left/right arrow keys)

Loading...

Bling Bling!

Building Interactive Plone Sites using KSS and JQuery


David Glick

Poll: Who has used Javascript?

Simple Javascript inclusion

<tal:block metal:fill-slot="javascript_head_slot">
    <script type="text/javascript" src="myscript.js"></script>
</tal:block>

When is this appropriate?

Resource Registry

(/portal_javascripts in ZMI)

Why use the resource registry?

Using external javascripts

Google AJAX Libraries API: http://code.google.com/apis/ajaxlibs/

Deferred loading of inline javascripts

Instead of this:
<script language="JavaScript" src="http://itde.vccs.edu/rss2js/feed2js.php?src=http%3A%2F%2Fdavid.wglick.org%2Fcategory%2Fplone%2Ffeed%2F&chan=title&num=3&desc=0&date=y&targ=n" type="text/javascript"></script>
Do this:
<head>
    <script type="text/javascript" language="Javascript">
        jq(function() {
            setTimeout(function() {
                jq('<script language="JavaScript" src="http://itde.vccs.edu/rss2js/feed2js.php?src=http%3A%2F%2Fdavid.wglick.org%2Fcategory%2Fplone%2Ffeed%2F&chan=title&num=3&desc=0&date=y&targ=n" type="text/javascript"><' + '/script>').appendTo('#feed');
            }, 5);
        });
    </script>
</head>
<div id="feed">

Must-have Javascript development tools

Javascript libraries

Goals:

KSS (Kinetic Style Sheets)

Javascript/AJAX framework that lets you declare behaviors using CSS-like syntax

KSS rules

body:click {
    action-client: alert;
    alert-message: "Curse your sudden but inevitable betrayal!";
}

KSS Documentation

kssproject.org
Professional Plone Development
Joel Burton's presentation

Example:

bullitt.org Grant History

The template

<div id="grantee-listing"
    metal:define-macro="grantee_listing"
    tal:define="search_view search_view | context/@@grantee_search;
                title    options/title    | request/title    |nothing;
                location options/location | request/location |nothing;
                year     options/year     | request/year     |nothing;
                program  options/program  | request/program  |nothing;
                items python:search_view.search(title=title, location=location, year=year, program=program);
                item_name string:grantees">

    <div tal:condition="not:items">
        No <span tal:replace="item_name"/> matched your criteria.  Please try again with a broader filter.
    </div>

    <div id="search-count"
         tal:condition="items">Showing 
        <strong tal:content="python:len(items)">41</strong>
        <span tal:replace="item_name"/>
    </div>

    <table class="search-results" id="grantee-results"
        tal:condition="items">
        <tbody>
            <tal:item_loop repeat="item items">
            <tr class="grantee-row">
                <td>
                    <div class="grantee-title" tal:content="item/title" />

                    <ul class="grantee-list">
                        <li tal:repeat="grant item/grants">
                            <a tal:attributes="href grant/url">
                                <span class="grantee-year" tal:content="grant/year" />
                                <span class="grantee-amount" tal:condition="grant/amount">
                                    &mdash; <span tal:content="string:$$${grant/amount}" />
                                </span>
                                <span class="grantee-project" tal:condition="grant/project">
                                    &mdash;
                                    <tal:block tal:replace="grant/project"/>
                                </span>
                            </a>
                        </li>
                    </ul>
                </td>
            </tr>
            </tal:item_loop>
        </tbody>
    </table>
</div>

The KSS

#grantee-search select:change {
    action-server: grantee_kss_search;
    grantee_kss_search-kssSubmitForm: currentForm();
}
#grantee-search input[type="text"]:blur {
    action-server: grantee_kss_search;
    grantee_kss_search-kssSubmitForm: currentForm();
}
#grantee-search input[type="submit"]:click {
    evt-click-preventdefault: true;
    action-server: grantee_kss_search;
    grantee_kss_search-kssSubmitForm: currentForm();
}
#grantee-search input[type="reset"]:click {
    action-server: grantee_kss_search;
}

The server action

<browser:page
    for="..content.GranteeFolder.GranteeFolder"
    name="grantee_kss_search"
    class=".search.GranteeSearchKSSView"
    attribute="search_grantees"
    permission="zope2.View"
    layer="..interfaces.IBullittGranteesBrowserLayer"
    />
class GranteeSearchKSSView(PloneKSSView):

    @kssaction
    def search_grantees(self):
        """Refresh the page with a new list of grantees"""
        core = self.getCommandSet('core')
        args = {}
        fields = ("title","location","year","program")
        for field in fields:
            args[field] = self.request.form.get(field, None)

        core.replaceHTML('#grantee-listing', 
                         self.context.grantee_listing_macro(**args))

jsregistry.xml rules to enable anonymous KSS

<?xml version="1.0"?>
<object name="portal_javascripts" meta_type="JavaScripts Registry"
   autogroup="False" purge="False">

<!-- Enable KSS for anon -->
<javascript cacheable="True" compression="safe" cookable="True"
   enabled="True" expression="python:not here.restrictedTraverse('@@kss_devel_mode').isoff()"
   id="++resource++kukit.js" inline="False" />
<javascript cacheable="True" compression="safe" cookable="True"
   enabled="True" expression="python:here.restrictedTraverse('@@kss_devel_mode').ison()"
   id="++resource++kukit-devel.js"
   inline="False"/>

<!-- IE 6 requires Sarissa, too -->
<javascript cacheable="True" compression="safe" cookable="True"
   enabled="True" expression="" id="sarissa.js" inline="False"/>

</object>

Reasons to think twice before using KSS

JQuery

Selectors

$('p')
$('p > a')
$('input:button')

Manipulation

$('p').before('*')

Chaining

$('p:first').next().addClass('second_paragraph').before('*')

Events

$('a').mouseover(function() {
    $(this).fadeOut();
});

More on JQuery

http://docs.jquery.com/Main_Page

JQuery in Plone

Integrating JQuery plugins that use $

Example:

This Presentation

The HTML

<div class="slides">
    <div>Slide 1</div>
    <div>Slide 2</div>
</div>

The Javascript

<script src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
    google.load("jquery", "1.3.2");
    google.setOnLoadCallback(function() {
        // hide all divs except the first one
        $('.slides div:not(:first-child)').hide();

        var advance = function() {
            // show next slide unless at end
            $('.slides div:visible:not(:last-child)').fadeOut().next().fadeIn();
        };
        var retreat = function() {
            // show prev slide unless at start
            $('.slides div:visible:not(:first-child)').fadeOut().prev().fadeIn();
        };

        $(document).keydown(function(e) {
            // show next slide on right arrow, unless at end
            if (e.keyCode == 39) { advance(); }
            // show prev slide on left arrow, unless at start
            if (e.keyCode == 37) { retreat(); }
        });
        $(document).click(function() {
           advance();
        });
    });
</script>

Example:

Image rollovers

Kupu styles

"Before" image|img|imgswap-before
"After" image|img|imgswap-after

imgswap.js

jq(function () {
    jq('.imgswap-after').css({ position: 'absolute', display: 'none' });
    jq('.imgswap-after').each(function(n) { jq(this).insertBefore(jq(this).prev('.imgswap-before')); });
    jq('.imgswap-before').mouseover(function() { jq(this).prev('.imgswap-after').fadeIn(300); });
    jq('.imgswap-after').mouseout(function() { jq(this).fadeOut(300); });
});

Reusable behaviors in Plone core

(see plone_ecmascript skin layer in CMFCore)

Example:

Updating an Archetypes widget

Archetypes schema
TopicPageSchema = schemata.ATContentTypeSchema.copy() + atapi.Schema((

    atapi.StringField('topic',
        widget = atapi.SelectionWidget(
            label=_(u'Topic'),
            description = _(u'Select an existing topic.  (These topics can be defined '
                            u'via the Vocabulary Library in Site Setup.)'),
            helper_js = ('++resource++update_topic_page_articles.js',),
            ),
        vocabulary_factory='yes.content.vocabularies.CategoriesVocabulary',
        ),

    OrderableReferenceField('related_items_1',
        relationship='topicpage_article_1',
        allowed_types = ('Article',),
        widget = OrderableReferenceWidget(
            label = _(u'2. First list of articles'),
            ),
        vocabulary = 'getVocabForItems',
        ),

))

Archetypes vocab getter

    def getVocabForItems(self):
        """ Find articles matching the configured topic.
        """
        topic = self.REQUEST.get('topic', self.topic)
        if not topic:
            return ()
        
        catalog = getToolByName(self, 'portal_catalog')
        brains = catalog.searchResults(
            portal_type = 'Article',
            category = topic,
            )
        return atapi.DisplayList([(b.UID, b.Title) for b in brains])

update_topic_page_articles.js

jq(function() {
    jq('#topic').change(function() {
        var topic = jq(this).val();
        var location = window.location.href.substring(0, window.location.href.length - 5);  // strip off '/edit'
        jq('#archetypes-fieldname-related_items_1').load(location+'/@@widget?field_name=related_items_1&mode=edit&topic=' + topic);
    });
});
ZCML:
<browser:resource
    name="update_topic_page_articles.js"
    file="javascript/update_topic_page_articles.js"
    />

render_widget.pt

<tal:defines metal:use-macro="context/global_defines/macros/defines"/>
<tal:more_defines tal:define="errors python:{}">
    <tal:block metal:use-macro="python:context.widget(**request.form)"/>
</tal:more_defines>
ZCML:
<browser:page
    name="widget"
    for="Products.Archetypes.interfaces.IBaseObject"
    template="render_widget.pt"
    permission="zope.Public"
    />

"Javascripty" Plone add-ons

from __future__ import javascript