Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Multiple themes #244

Open
wants to merge 14 commits into from

2 participants

@jesstelford

This branch implements the ability to use multiple themes (one theme per gallery) when there are multiple galleries.

This should be a backward compatible update - all existing galleries (even those with multiple galleries per page) should still work correctly.

The basic premise of this functionality is to 'namespace' each theme's CSS file with an id by injecting the id into all rules in the file, and adding that id to the .galleria-container element.

Each theme has a unique id based on its name, allowing them to be specified as part of the options list (see documentation for 'theme' option).

@jesstelford

This addresses this Get Satisfaction question, and issue #105.

jesstelford added some commits
@jesstelford jesstelford Bad IE selectors now only effect IE c6226dd
@jesstelford jesstelford Protect against WebKit bug #73152 when inserting quoted CSS
https://bugs.webkit.org/show_bug.cgi?id=73152
solution derived from https://gist.github.com/1420621

When 'namespacing' the CSS and isnerting it in Webkit, any styles with
a quote had their quotes stripped by WebKit when read, resulting in
invalid CSS inserted back into the CSSOM. This commit works around this
bug.
98de0e1
@Ciantic

This should be fixed asap. Couldn't believe when I noticed it can't run multiple themes on same page.

Ideally one should be able to include all themes using normal link and script tags in advance and simply choose the theme in the options.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 21, 2012
  1. @jesstelford
  2. @jesstelford
  3. @jesstelford
Commits on Feb 22, 2012
  1. @jesstelford

    Remove dead variable

    jesstelford authored
  2. @jesstelford

    Store loaded themes in a global array

    jesstelford authored
    This will allow us to switch themes on a per instance basis.
  3. @jesstelford

    Don't overwrite any previous timer.

    jesstelford authored
    Stop previous timers for delayed loading being overwritten when loadTheme()
    called quickly in succession in user code.
Commits on Feb 24, 2012
  1. @jesstelford

    'Namespace' CSS Rules on a per-theme basis

    jesstelford authored
    This is to stop multiple themes from having conflicting styles on the same page
    without the need for an iframe
  2. @jesstelford
  3. @jesstelford
  4. @jesstelford
  5. @jesstelford

    Update to documentation

    jesstelford authored
  6. @jesstelford
Commits on Feb 27, 2012
  1. @jesstelford
Commits on Mar 12, 2012
  1. @jesstelford

    Protect against WebKit bug #73152 when inserting quoted CSS

    jesstelford authored
    https://bugs.webkit.org/show_bug.cgi?id=73152
    solution derived from https://gist.github.com/1420621
    
    When 'namespacing' the CSS and isnerting it in Webkit, any styles with
    a quote had their quotes stripped by WebKit when read, resulting in
    invalid CSS inserted back into the CSSOM. This commit works around this
    bug.
This page is out of date. Refresh to see the latest.
Showing with 336 additions and 31 deletions.
  1. +2 −0  .gitignore
  2. +35 −0 docs/options/theme.rst
  3. +299 −31 src/galleria.js
View
2  .gitignore
@@ -4,3 +4,5 @@ _build
*.swp
*.swo
docs/jsdoc/html
+src/themes/folio
+src/index.html
View
35 docs/options/theme.rst
@@ -0,0 +1,35 @@
+============
+theme
+============
+
+ | type: **String**
+ | default: **undefined**
+
+The theme that should be used for this gallery.
+
+If undefined, this will default to the most recently loaded theme.
+
+The name should be in the same format as the theme's css/js files: 'galleria.themename'.
+
+The theme must be loaded (either before or after) by calling 'Galleria.loadTheme'.
+
+.. highlight:: html
+
+Example::
+
+ <script>
+ Galleria.loadTheme('themes/classic/galleria.classic.js');
+ $('#galleria').galleria({
+ theme: 'galleria.classic'
+ });
+ </script>
+
+Alternatively, it is possible to simply call 'loadTheme' directly before the gallery is initialized::
+
+ <script>
+ Galleria.loadTheme('themes/classic/galleria.classic.js');
+ $('#galleria-one').galleria(); // Will have the 'classic' theme
+
+ Galleria.loadTheme('themes/twelve/galleria.twelve.js');
+ $('#galleria-two').galleria(); // Will have the 'Twelve' theme
+ </script>
View
330 src/galleria.js
@@ -213,14 +213,32 @@ var undef,
// instance pool, holds the galleries until themeLoad is triggered
_pool = [],
+ // The themes that have been loaded
+ _themes = {},
+
+ // default (most recently loaded) theme
+ _defaultTheme = undef,
+
// themeLoad trigger
_themeLoad = function( theme ) {
- Galleria.theme = theme;
+
+ // fix the theme name so it can safely be used as an object property
+ var themeName = theme.css.replace(/(.*\/)?([^\/]+?)(\.min)?\.\w+$/, '$2').replace(/[^\w]/, '');
+ if (!(themeName in _themes && _themes.hasOwnProperty(themeName))) {
+ _themes[themeName] = theme;
+ }
+
+ _defaultTheme = themeName;
// run the instances we have in the pool
$.each( _pool, function( i, instance ) {
if ( !instance._initialized ) {
- instance._init.call( instance );
+ if (instance._theme === undef) {
+ instance._theme = _defaultTheme;
+ }
+ if (instance._theme == themeName) {
+ instance._init.call( instance );
+ }
}
});
},
@@ -764,7 +782,10 @@ var undef,
var link,
ready = false,
- length;
+ length,
+ // reduce href to a valid css name for uniquely
+ // identifying it
+ themeName = href.replace(/(.*\/)?([^\/]+?)(\.min)?\.\w+$/, '$2').replace(/[^\w]/, '');
// look for manual css
$('link[rel=stylesheet]').each(function() {
@@ -783,6 +804,12 @@ var undef,
// if already present, return
if ( link ) {
+
+ if (!$(link).data('themeified')) {
+ Utils.prependCssId(link, themeName);
+ $(link).data('themeified') = true;
+ }
+
callback.call( link, link );
return link;
}
@@ -847,25 +874,257 @@ var undef,
}, 10);
}
- if ( typeof callback === 'function' ) {
+ Utils.wait({
+ until: function() {
+ return ready && doc.styleSheets.length > length;
+ },
+ success: function() {
+ Utils.prependCssId(link, themeName);
+ $(link).data('themeified', true);
- Utils.wait({
- until: function() {
- return ready && doc.styleSheets.length > length;
- },
- success: function() {
+ if ( typeof callback === 'function' ) {
window.setTimeout( function() {
callback.call( link, link );
}, 100);
- },
- error: function() {
- Galleria.raise( 'Theme CSS could not load', true );
- },
- timeout: 10000
- });
- }
+ }
+ },
+ error: function() {
+ Galleria.raise( 'Theme CSS could not load', true );
+ },
+ timeout: 10000
+ });
+
return link;
+ },
+
+ prependCssId: function(element, id) {
+
+ var d = document,
+ p = d.createElement('p'), // Have to hold the element (see notes)
+ workerStyle = p.style, // worker style collection
+ head,
+ element, // The HTMLElement
+ sheet, // browser agnostic access to styleSheet DOM element
+ _rules, // browser agnostic access to 'rules' element of 'sheet'
+ _insertRule, // browser agnostic access to 'insertRule' function of 'sheet'
+ _deleteRule, // browser agnostic access to 'deleteRule' function of 'sheet'
+ i,
+ m,
+ lastIndex,
+ rule,
+ style,
+ quotedStyle,
+ selectors,
+ position,
+ validSelectors;
+
+ // Begin setting up private aliases to the important moving parts
+ // 1. The stylesheet object
+ // IE stores StyleSheet under the "styleSheet" property
+ // Safari doesn't populate sheet for xdomain link elements
+ sheet = element.sheet || element.styleSheet;
+
+ // 2. The style rules collection
+ // IE stores the rules collection under the "rules" property
+ _rules = sheet && ('cssRules' in sheet) ? 'cssRules' : 'rules';
+
+ // 3. The method to remove a rule from the stylesheet
+ // IE supports removeRule
+ _deleteRule = ('deleteRule' in sheet) ?
+ function (i) { sheet.deleteRule(i); } :
+ function (i) { sheet.removeRule(i); };
+
+ // 4. The method to add a new rule to the stylesheet
+ // IE supports addRule with different signature
+ _insertRule = ('insertRule' in sheet) ?
+ function (sel,css,i) { sheet.insertRule(sel+" {"+css+"}",i); } :
+ function (sel,css,i) { sheet.addRule(sel,css,i); };
+
+ // 5. Initialize the cssRules map from the element
+ // xdomain link elements forbid access to the cssRules collection, so this
+ // will throw an error.
+ lastIndex = sheet[_rules].length - 1;
+
+ id = "#" + id;
+
+ // for each rule in the sheet, in reverse order
+ for (index = lastIndex; index >= 0; --index) {
+
+ rule = sheet[_rules][lastIndex];
+ style = Utils.getQuotedWebKitCssText('content', rule.style);
+ // IE's addRule doesn't support multiple comma delimited selectors
+ selectors = rule.selectorText.split(/\s*,\s*/);
+ validSelectors = 0;
+
+ _deleteRule(lastIndex);
+
+ for (m = selectors.length - 1; m >= 0; --m) {
+ // Some selector values can cause IE to hang
+ if (IE !== undef && !Utils.isValidSelector(selectors[m])) {
+ console.log("Invalid: " + selectors[m] + ": {" + style + "}");
+ continue;
+ }
+
+ // Prepend the id of the container element to all
+ // rules
+ position = selectors[m].indexOf(".galleria-container");
+
+ if (position == -1) {
+ position = selectors[m].indexOf(".notouch");
+ if (position == -1) {
+ position = selectors[m].indexOf(".touch");
+ }
+ }
+
+ // note the space
+ if (position == -1) {
+ selectors[m] = id + " " + selectors[m];
+ } else if (position == 0) {
+ selectors[m] = id + selectors[m];
+ } else {
+ selectors[m] = selectors[m].substring(0, position) + id + selectors[m].substring(position);
+ }
+
+ /*
+ * Opera throws an error if there's a syntax error in assigned
+ * cssText. Avoid this using a worker style collection, then
+ * assigning the resulting cssText.
+ *
+ * A very difficult to repro/isolate IE 9 beta (and Platform Preview 7) bug
+ * was reduced to this line throwing the error:
+ * "Invalid this pointer used as target for method call"
+ * It appears that the style collection is corrupted. The error is
+ * catchable, so in a best effort to work around it, replace the
+ * p and workerStyle and try the assignment again.
+ */
+ try {
+ workerStyle.cssText = style || '';
+ } catch (e) {
+ p = d.createElement('p');
+ workerStyle = p.style;
+ workerStyle.cssText = style || '';
+ }
+
+ quotedStyle = Utils.getQuotedWebKitCssText('content', workerStyle);
+
+ if (!quotedStyle) {
+ console.log("Skipping : " + selectors[m] + ": {" + quotedStyle + "}");
+ console.log("Should be: " + selectors[m] + ": {" + style + "}");
+ continue;
+ }
+
+
+ _insertRule(
+ selectors[m],
+ quotedStyle,
+ 0
+ );
+
+ validSelectors++;
+ }
+
+ // if there is anything but one selector, we've changed the size of
+ // the rules array, so adjust accordingly
+ lastIndex += (validSelectors - 1);
+
+ }
+
+ },
+
+ /**
+ * Determines if a CSS selector string is safe to use. Used
+ * in set to prevent IE from locking up when attempting to add a rule for a
+ * &quot;bad selector&quot;.
+ *
+ * Bad selectors are considered to be any string containing unescaped
+ * `~!@$%^&()+=|{}[];'"?< or space. Also forbidden are . or # followed by
+ * anything other than an alphanumeric. Additionally -abc or .-abc or
+ * #_abc or '# ' all fail. There are likely more failure cases, so
+ * please file a bug if you encounter one.
+ *
+ * @method isValidSelector
+ * @param sel {String} the selector string
+ * @return {Boolean}
+ * @static
+ */
+ isValidSelector : function (sel) {
+ var valid = false;
+
+ if (sel && typeof sel === "string") {
+
+ // TEST: there should be nothing but white-space left after
+ // these destructive regexs
+ valid = !/\S/.test(
+ // combinators
+ sel.replace(/\s+|\s*[+~>]\s*/g,' ').
+ // attribute selectors (contents not validated)
+ replace(/([^ ])\[.*?\]/g,'$1').
+ // pseudo-class|element selectors (contents of parens
+ // such as :nth-of-type(2) or :not(...) not validated)
+ replace(/([^ ])::?[a-z][a-z\-]+[a-z](?:\(.*?\))?/ig,'$1').
+ // element tags
+ replace(/(?:^| )[a-z0-6]+/ig,' ').
+ // escaped characters
+ replace(/\\./g, '').
+ // class and id identifiers
+ replace(/[.#]\w[\w\-]*/g, ''));
+ }
+
+ return valid;
+ },
+
+ /**
+ * Protect against WebKit bug #73152
+ *
+ * https://bugs.webkit.org/show_bug.cgi?id=73152
+ * solution derived from https://gist.github.com/1420621
+ *
+ * @param string property The property to escape (eg; "content")
+ * @param CSSValue style The style block to retrieve cssText from
+ */
+ getQuotedWebKitCssText : function (property, style)
+ {
+
+ var cssText = style.cssText,
+ escapedStyleVals = [],
+ styleValues,
+ styleValue,
+ styleValueCssText,
+ escapedCssText,
+ styleRegex;
+
+ if (Galleria.WEBKIT && (styleValues = style.getPropertyCSSValue("content"))) {
+
+ // make sure we have an array of values for the property
+ if (styleValues.cssValueType !== styleValues.CSS_VALUE_LIST) {
+ styleValues = [styleValues];
+ }
+
+ // for each value
+ for (m = 0; m < styleValues.length; m++) {
+ styleValue = styleValues[m];
+ styleValueCssText = styleValue.cssText;
+
+ // that isn't quoted
+ if (styleValue.primitiveType === styleValue.CSS_STRING && !/^\s*["']/.test(styleValueCssText)) {
+ // surround value with single quotes
+ styleValueCssText = "'" + styleValueCssText + "'";
+ }
+
+ escapedStyleVals.push(styleValueCssText);
+ }
+
+ // reconstruct the property / values pair
+ escapedCssText = "content:" + escapedStyleVals.join(" ");
+
+ // replace the original with the new pair
+ styleRegex = new RegExp("content: " + styleValues.cssText);
+ cssText = cssText.replace(styleRegex, escapedCssText);
+ }
+
+ return cssText;
}
+
};
}()),
@@ -1028,9 +1287,6 @@ Galleria = function() {
var self = this;
- // the theme used
- this._theme = undef;
-
// internal options
this._options = {};
@@ -1071,6 +1327,8 @@ Galleria = function() {
// target holder
this._target = undef;
+ this._theme = undef;
+
// instance id
this._id = Math.random();
@@ -1735,7 +1993,7 @@ Galleria = function() {
hide : function() {
- if ( !self._options.idleMode || self.getData().iframe ) {
+ if ( !self._options.idleMode || self.getIndex() === false || self.getData().iframe ) {
return;
}
@@ -2166,6 +2424,7 @@ Galleria.prototype = {
showCounter: true,
showImagenav: true,
swipe: true, // 1.2.4
+ theme: undef,
thumbCrop: true,
thumbEventType: 'click',
thumbFit: true,
@@ -2215,8 +2474,14 @@ Galleria.prototype = {
// hide all content
$( this._target ).children().hide();
+ if (options && typeof options.theme !== 'undefined') {
+ this._theme = options.theme.replace(/[^\w]/, '');
+ } else {
+ this._theme = _defaultTheme;
+ }
+
// now we just have to wait for the theme...
- if ( typeof Galleria.theme === 'object' ) {
+ if (this._theme !== undef && (this._theme in _themes && _themes.hasOwnProperty(this._theme))) {
this._init();
} else {
// push the instance into the pool and run it when the theme is ready
@@ -2234,20 +2499,23 @@ Galleria.prototype = {
var self = this,
options = this._options;
- if ( this._initialized ) {
- Galleria.raise( 'Init failed: Gallery instance already initialized.' );
+ if (_themes[this._theme] === undef) {
+ Galleria.raise( 'Init failed: No theme found.' );
return this;
}
- this._initialized = true;
+ // set the theme ID on the element
+ this.$( 'container' ).attr("id", this._theme);
- if ( !Galleria.theme ) {
- Galleria.raise( 'Init failed: No theme found.' );
+ if ( this._initialized ) {
+ Galleria.raise( 'Init failed: Gallery instance already initialized.' );
return this;
}
+ this._initialized = true;
+
// merge the theme & caller options
- $.extend( true, options, Galleria.theme.defaults, this._original.options );
+ $.extend( true, options, _themes[this._theme].defaults, this._original.options );
// check for canvas support
(function( can ) {
@@ -2901,7 +3169,7 @@ Galleria.prototype = {
self.trigger( Galleria.READY );
// call the theme init method
- Galleria.theme.init.call( self, self._options );
+ _themes[self._theme].init.call( self, self._options );
// call the extend option
self._options.extend.call( self, self._options );
@@ -4563,7 +4831,7 @@ Galleria.addTheme = function( theme ) {
// we have a match
css = script.src.replace(/[^\/]*$/, '') + theme.css;
- Utils.addTimer( "css", function() {
+ Utils.addTimer( undef, function() {
Utils.loadCSS( css, 'galleria-theme', function() {
// the themeload trigger
@@ -4603,8 +4871,8 @@ Galleria.loadTheme = function( src, options ) {
Galleria.raise( "Theme at " + src + " could not load, check theme path.", true );
}, 5000 );
- // first clear the current theme, if exists
- Galleria.theme = undef;
+ // Treat this theme as the new 'default'
+ _defaultTheme = src.replace(/(.*\/)?([^\/]+?)(\.min)?\.\w+$/, '$2').replace(/[^\w]/, '');
// load the theme
Utils.loadScript( src, function() {
Something went wrong with that request. Please try again.