Simple image roll-over effect with jQuery

Here is a simple jQuery method to enable image roll-over effects (hover). This method is suitable for content editors who can add images only through TinyMCE or normal upload –  naming image files specially is the only requirement. No CSS, Javascript or other knowledge needed by the person who needs to add the images with roll-overs.

Just include this script on your HTML page and it will automatically scan image filenames, detects image filenames with special roll-over marker strings and then applies the roll-over effect on them. Roll-over images are preloaded to avoid image blinking on slow connections.

The script:

/**
 * Automatic image hover placement with jQuery
 *
 * If image has -normal tag in it's filename assume there exist corresponding
 * file with -hover in its name.
 *
 * E.g. http://host.com/test-normal.gif -> http://host.com/test-hover.gif
 *
 * This image is preloaded and shown when mouse is placed on the image.
 *
 * Copyright Mikko Ohtamaa 2011
 *
 * http://twitter.com/moo9000
 */

(function (jQuery) {
        var $ = jQuery;

        // Look for available images which have hover option
        function scanImages() {
                $("img").each(function() {

                        $this = $(this);

                        var src = $this.attr("src");

                        // Images might not have src attribute, if they
                        if(src) {

                                // Detect if this image filename has hover marker bit
                                if(src.indexOf("-normal") >= 0) {

                                        console.log("Found rollover:" + src);

                                        // Mangle new URL for over image based on orignal
                                        var hoverSrc = src.replace("-normal", "-hover");

                                        // Preload hover image
                                        var preload = new Image(hoverSrc);

                                        // Set event handlers

                                        $this.mouseover(function() {
                                                this.src = hoverSrc;
                                        });

                                        $this.mouseout(function() {
                                                this.src = src;
                                        });

                                }
                        }
                });
        }

        $(document).ready(scanImages);

})(jQuery);

\"\" Subscribe to RSS feed Follow me on Twitter Follow me on Facebook Follow me Google+

Centrally settings Products.Carousel image widths in Plone

Another example to manipulate Products.Carousel. This script will update all carousel settings on the site to have new image width.. 

class SetCarouselWidths(BrowserView):
    """
    Set width to all carousels on the site.
    """

    def __call__(self):
        """

        """

        self.buffer = StringIO()

        print >> self.buffer, "Log output"

        brains = self.context.portal_catalog(portal_type="Folder")
        for b in brains:
            obj = b.getObject()
            if "carousel" in obj.objectIds():
                carousel = obj["carousel"]
                # Carousel installed on this folder
                settings = ICarouselSettings(carousel)
                print >> self.buffer, "Setting width for " + carousel.absolute_url()
                settings.width = 680

        print >> self.buffer, "All carousels updated"

        return self.buffer.getvalue()

ZCML:

<browser:page
  for="*"
  name="set_carousel_widths"
  permission="cmf.ManagePortal"
  class=".carousel.SetCarouselWidths"
  />

\"\" Subscribe to RSS feed Follow me on Twitter Follow me on Facebook Follow me Google+

Migration script from Products.Slideshow to Products.Carousel

Here is a sample migration code to transform your site from one add-on to another.

We create a migration view which you can call by typing in view name manually to web browser.

This code will

  • Scan site for folders which have Slideshow add-on enabled. In this example we check against a predefined list (scanned earlier), but the code contains example how to detect slideshow folders
  • Create Carousel for those folders
  • Create corresponds Carousel Banners for all Slideshow Image content items
  • Set some Carousel settings
  • Make sure that we invalidate cache for content items going through migration
  • Set a new default view for folders which were using slideshow

Also

  • After inspecting the process was ok you can delete migrated images

carousel.py:

"""

    Migrate slideshow to carousel.

    Usage:

    http://yoursite/@@migrate_carousel - the process can be repeated with adjusted settings. It's non-descructive.

    http://yoursite/@@delete_migrated_slideshow_images

"""

import logging
from StringIO import StringIO
from Products.Five.browser import BrowserView

from zope.component import getUtility, getMultiAdapter
from zope.app.component.hooks import setHooks, setSite, getSite

from zope.interface import alsoProvides
from Products.Five import BrowserView
from Products.CMFCore.utils import getToolByName
from Products.Carousel.interfaces import ICarouselFolder
from Products.Carousel.utils import addPermissionsForRole
from Products.Carousel.config import CAROUSEL_ID
from Products.Carousel.interfaces import ICarousel, ICarouselSettings

from Products.slideshowfolder.interfaces import ISlideShowSettings, ISlideShowView, IFolderSlideShowView, ISlideShowFolder, ISlideshowImage

logger = logging.getLogger("Slideshow Migrator")

FOLDER_PATHS_TO_MIGRATE="""
('', 'site', 'folder1')
('', 'site', 'folder2')
('', 'site', 'folder2', 'subfolder')
"""

class MigrateSlideshowToCarousel(BrowserView):
    """
    Migrate collective.slideshow to Products.Carousel
    """

    def startCapture(self, newLogLevel = None):
        """ Start capturing log output to a string buffer.

        http://docs.python.org/release/2.6/library/logging.html

        @param newLogLevel: Optionally change the global logging level, e.g. logging.DEBUG
        """
        self.buffer = StringIO()

        print >> self.buffer, "Log output"

        rootLogger = logging.getLogger()

        if newLogLevel:
            self.oldLogLevel = rootLogger.getEffectiveLevel()
            rootLogger.setLevel(newLogLevel)
        else:
            self.oldLogLevel = None

        self.logHandler = logging.StreamHandler(self.buffer)
        formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
        self.logHandler.setFormatter(formatter)
        rootLogger.addHandler(self.logHandler)

    def stopCapture(self):
        """ Stop capturing log output.

        @return: Collected log output as string
        """

        # Remove our handler
        rootLogger = logging.getLogger()

        # Restore logging level (if any)
        if self.oldLogLevel:
            rootLogger.setLevel(self.oldLogLevel)

        rootLogger.removeHandler(self.logHandler)

        self.logHandler.flush()
        self.buffer.flush()

        return self.buffer.getvalue()

    def getImages(self, folder):
        """ Get all ATImages in a folder """
        for obj in folder.objectValues():
            if obj.portal_type == "Image":
                yield obj

    def getOrCreateCarousel(self, folder):
        """ Copied from Products.Carousel manager.py """

        if hasattr(folder.aq_base, CAROUSEL_ID):
            logger.info("Using existing carousel in " + str(folder))
            carousel = getattr(folder, CAROUSEL_ID)
        else:
            logger.info("Creating carousel in " + str(folder))
            pt = getToolByName(folder, 'portal_types')
            newid = pt.constructContent('Folder', folder, 'carousel', title='Carousel Banners', excludeFromNav=True)
            carousel = getattr(folder, newid)

            # mark the new folder as a Carousel folder
            alsoProvides(carousel, ICarouselFolder)

            # make sure Carousel banners are addable within the new folder
            addPermissionsForRole(carousel, 'Manager', ('Carousel: Add Carousel Banner',))

            # make sure *only* Carousel banners are addable
            carousel.setConstrainTypesMode(1)
            carousel.setLocallyAllowedTypes(['Carousel Banner'])
            carousel.setImmediatelyAddableTypes(['Carousel Banner'])

        return carousel

    def imageToCarouselBanner(self, image, carousel):
        """
        Convert ATImage to Carousel Banner content item.
        """

        logger.info("Migrating slideshow image:" + str(image.getId()))

        id = image.getId()

        if not id in carousel.objectIds():
            carousel.invokeFactory("Carousel Banner", id, title=image.Title())
        else:
            logger.info("Carousel image already existed " + str(image))

        banner = carousel[id]

        # Copy over image field from ATImage content type
        banner.setImage(image.getImage())

        # Set a hidden flag which allows us later to delete images
        image._migrated_to_carousel = True

    def setupCarousel(self, carousel_folder):
        """
        Set-up custom carousel settings for all carousels.
        """

        logger.info("Setting carousel settings for:" + carousel_folder.absolute_url())

        settings = ICarouselSettings(carousel_folder)

        settings.width = 640
        settings.height = 450
        settings.pager_template = u'@@pager-none'
        settings.default_page_only = False
        settings.element_id = "karuselli"
        settings.transition_delay = 5.0
        settings.banner_elements = [ u"image" ]

    def migrateFolder(self, folder):
        """ Migrate one folder from Slideshow to Products.Carousel
        """
        logger.info("Migrating folder:" + str(folder))

        carousel = self.getOrCreateCarousel(folder)

        self.setupCarousel(carousel)

        images = self.getImages(folder)
        for image in images:
            self.imageToCarouselBanner(image, carousel)

        # This will toggle cache refresh for the object
        # if Products.CacheSetup is used -> should invalidate template cache.
        # Not necessary if Products.CacheSetup is not installed.
        folder.setTitle(folder.Title())
        folder.reindexObject()

        # Toggle folder away from slideshow view
        # empty_view is our custom view which does not list folder contents
        folder.setLayout("empty_view")

        # Set a marker flag in the case we need to play around with these
        # folderes programmatically in the future
        folder._migrated_to_carousel = True

    def migrate(self):
        """
        Run the migration process for one Plone site.
        """

        brains = self.context.portal_catalog(portal_type="Folder")

        # Use predefined report of slideshow folder on old site
        # Alternative: detect slideshow folders as shown below
        carousel_folders  = FOLDER_PATHS_TO_MIGRATE.split("\n")

        for b in brains:

            obj = b.getObject()

            path = str(obj.getPhysicalPath())

            # Alternative: if you don't have fixed list check here if getattr(obj, "default_view", "") == "slideshow_view"
            if path in carousel_folders:
                self.migrateFolder(obj)

    def __call__(self):
        """ Process the form.

        Process the form, log the output and show the output to the user.
        """

        self.logs = None

        try:
            self.startCapture(logging.DEBUG)

            logger.info("Starting full site migration")

            # Do the long running,
            # lots of logging stuff
            self.migrate()

            logger.info("Succesfully done")

        except Exception, e:
            # Show friendly error message
            logger.exception(e)

        # Put log output for the page template access
        self.logs = self.stopCapture()

        return self.logs

class DeleteMigratedImages(BrowserView):
    """
    Delete all slideshow image files which have been migrated to carousel banners.

    By doing migration in two phases allows us to adjust the process in the case it goes wrong.
    """

    def __call__(self):
        """

        """

        self.buffer = StringIO()

        print >> self.buffer, "Log output"

        brains = self.context.portal_catalog(portal_type="Image")
        for b in brains:
            obj = b.getObject()
            if getattr(obj, "_migrated_to_carousel", False) == True:
                print >> self.buffer, "Deleting migrated Image " + obj.getId()
                id = obj.getId()
                parent = obj.aq_parent
                parent.manage_delObjects([id])

        print >> self.buffer, "All migrated images deleted"

        return self.buffer.getvalue()

ZCML bits:

<browser:page
  for="*"
  name="migrate_carousel"
  permission="cmf.ManagePortal"
  class=".carousel.MigrateSlideshowToCarousel"
  />

<browser:page
  for="*"
  name="delete_migrated_slideshow_images"
  permission="cmf.ManagePortal"
  class=".carousel.DeleteMigratedImages"
  />

\"\" Subscribe to RSS feed Follow me on Twitter Follow me on Facebook Follow me Google+