ResourceLoaderImageModule: Implement CSS selector templates
authorBartosz Dziewoński <matma.rex@gmail.com>
Mon, 23 Mar 2015 21:04:54 +0000 (22:04 +0100)
committerBartosz Dziewoński <matma.rex@gmail.com>
Sun, 29 Mar 2015 17:25:02 +0000 (17:25 +0000)
Instead of a 'prefix', a 'selector' can be specified, with a string
containing a simple template to use for CSS selector. For example:

    'selector' => '.mw-ui-icon-{name}'

'prefix' continues to work as before.

When using variants, one might want to provide separate
'selectorWithoutVariant' and 'selectorWithVariant' options.

Available variables are {prefix}, {type}, {name}, and {variant}.

Bug: T78215
Change-Id: I99ccaf25e8d24fed5afd0c4b770d2f389789ce4b

includes/resourceloader/ResourceLoaderImageModule.php
tests/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php

index 3d65745..2faacd6 100644 (file)
@@ -38,7 +38,9 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
 
        protected $images = array();
        protected $variants = array();
-       protected $prefix = array();
+       protected $prefix = null;
+       protected $selectorWithoutVariant = '.{prefix}-{type}-{name}';
+       protected $selectorWithVariant = '.{prefix}-{type}-{name}-{variant}';
        protected $targets = array( 'desktop', 'mobile' );
 
        /**
@@ -57,6 +59,11 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
         *         'localBasePath' => [base path],
         *         // CSS class prefix to use in all style rules
         *         'prefix' => [CSS class prefix],
+        *         // Alternatively: Format of CSS selector to use in all style rules
+        *         'selector' => [CSS selector template, variables: {prefix} {type} {name} {variant}],
+        *         // Alternatively: When using variants
+        *         'selectorWithoutVariant' => [CSS selector template, variables: {prefix} {type} {name}],
+        *         'selectorWithVariant' => [CSS selector template, variables: {prefix} {type} {name} {variant}],
         *         // List of variants that may be used for the image files
         *         'variants' => array(
         *             // ([image type] is a string, used in generated CSS class
@@ -87,10 +94,29 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
        public function __construct( $options = array(), $localBasePath = null ) {
                $this->localBasePath = self::extractLocalBasePath( $options, $localBasePath );
 
-               if ( !isset( $options['prefix'] ) || !$options['prefix'] ) {
-                       throw new MWException(
-                               "Required 'prefix' option not given or empty."
-                       );
+               // Accepted combinations:
+               // * prefix
+               // * selector
+               // * selectorWithoutVariant + selectorWithVariant
+               // * prefix + selector
+               // * prefix + selectorWithoutVariant + selectorWithVariant
+
+               $prefix = isset( $options['prefix'] ) && $options['prefix'];
+               $selector = isset( $options['selector'] ) && $options['selector'];
+               $selectorWithoutVariant = isset( $options['selectorWithoutVariant'] ) && $options['selectorWithoutVariant'];
+               $selectorWithVariant = isset( $options['selectorWithVariant'] ) && $options['selectorWithVariant'];
+
+               if ( $selectorWithoutVariant && !$selectorWithVariant ) {
+                       throw new MWException( "Given 'selectorWithoutVariant' but no 'selectorWithVariant'." );
+               }
+               if ( $selectorWithVariant && !$selectorWithoutVariant ) {
+                       throw new MWException( "Given 'selectorWithVariant' but no 'selectorWithoutVariant'." );
+               }
+               if ( $selector && $selectorWithVariant ) {
+                       throw new MWException( "Incompatible 'selector' and 'selectorWithVariant'+'selectorWithoutVariant' given." );
+               }
+               if ( !$prefix && !$selector && !$selectorWithVariant ) {
+                       throw new MWException( "None of 'prefix', 'selector' or 'selectorWithVariant'+'selectorWithoutVariant' given." );
                }
 
                foreach ( $options as $member => $option ) {
@@ -121,8 +147,13 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
                                        break;
 
                                case 'prefix':
+                               case 'selectorWithoutVariant':
+                               case 'selectorWithVariant':
                                        $this->{$member} = (string)$option;
                                        break;
+
+                               case 'selector':
+                                       $this->selectorWithoutVariant = $this->selectorWithVariant = (string)$option;
                        }
                }
        }
@@ -135,6 +166,17 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
                return $this->prefix;
        }
 
+       /**
+        * Get CSS selector templates used by this module.
+        * @return string
+        */
+       public function getSelectors() {
+               return array(
+                       'selectorWithoutVariant' => $this->selectorWithoutVariant,
+                       'selectorWithVariant' => $this->selectorWithVariant,
+               );
+       }
+
        /**
         * Get a ResourceLoaderImage object for given image.
         * @param string $name Image name
@@ -233,7 +275,10 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
                // Build CSS rules
                $rules = array();
                $script = $context->getResourceLoader()->getLoadScript( $this->getSource() );
-               $prefix = $this->getPrefix();
+               $selectors = $this->getSelectors();
+
+               $needsTypeWithoutVariant = strpos( $selectors['selectorWithoutVariant'], '{type}' ) !== false;
+               $needsTypeWithVariant = strpos( $selectors['selectorWithVariant'], '{type}' ) !== false;
 
                foreach ( $this->getImages() as $name => $image ) {
                        $type = $this->getImageType( $name );
@@ -243,7 +288,17 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
                                $image->getUrl( $context, $script, null, 'rasterized' )
                        );
                        $declarations = implode( "\n\t", $declarations );
-                       $rules[] = ".$prefix-$type-$name {\n\t$declarations\n}";
+                       $selector = strtr(
+                               $selectors['selectorWithoutVariant'],
+                               array(
+                                       '{prefix}' => $this->getPrefix(),
+                                       '{name}' => $name,
+                                       '{variant}' => '',
+                                       // Somewhat expensive, don't compute if not needed
+                                       '{type}' => $needsTypeWithoutVariant ? $this->getImageType( $name ) : null,
+                               )
+                       );
+                       $rules[] = "$selector {\n\t$declarations\n}";
 
                        // TODO: Get variant configurations from $context->getSkin()
                        foreach ( $image->getVariants() as $variant ) {
@@ -252,7 +307,17 @@ class ResourceLoaderImageModule extends ResourceLoaderModule {
                                        $image->getUrl( $context, $script, $variant, 'rasterized' )
                                );
                                $declarations = implode( "\n\t", $declarations );
-                               $rules[] = ".$prefix-$type-$name-$variant {\n\t$declarations\n}";
+                               $selector = strtr(
+                                       $selectors['selectorWithVariant'],
+                                       array(
+                                               '{prefix}' => $this->getPrefix(),
+                                               '{name}' => $name,
+                                               '{variant}' => $variant,
+                                               // Somewhat expensive, don't compute if not needed
+                                               '{type}' => $needsTypeWithVariant ? $this->getImageType( $name ) : null,
+                                       )
+                               );
+                               $rules[] = "$selector {\n\t$declarations\n}";
                        }
                }
 
index f98f5fc..939016b 100644 (file)
@@ -98,6 +98,52 @@ class ResourceLoaderImageModuleTest extends ResourceLoaderTestCase {
 }
 .oo-ui-icon-bold-invert {
        ...
+}',
+                       ),
+                       array(
+                               array(
+                                       'class' => 'ResourceLoaderImageModule',
+                                       'selectorWithoutVariant' => '.mw-ui-icon-{name}:after, .mw-ui-icon-{name}:before',
+                                       'selectorWithVariant' => '.mw-ui-icon-{name}-{variant}:after, .mw-ui-icon-{name}-{variant}:before',
+                                       'variants' => array(
+                                               'icon' => $commonVariants,
+                                       ),
+                                       'images' => array(
+                                               'icon' => $commonImageData,
+                                       ),
+                               ),
+                               '.mw-ui-icon-advanced:after, .mw-ui-icon-advanced:before {
+       ...
+}
+.mw-ui-icon-advanced-invert:after, .mw-ui-icon-advanced-invert:before {
+       ...
+}
+.mw-ui-icon-remove:after, .mw-ui-icon-remove:before {
+       ...
+}
+.mw-ui-icon-remove-invert:after, .mw-ui-icon-remove-invert:before {
+       ...
+}
+.mw-ui-icon-remove-destructive:after, .mw-ui-icon-remove-destructive:before {
+       ...
+}
+.mw-ui-icon-next:after, .mw-ui-icon-next:before {
+       ...
+}
+.mw-ui-icon-next-invert:after, .mw-ui-icon-next-invert:before {
+       ...
+}
+.mw-ui-icon-help:after, .mw-ui-icon-help:before {
+       ...
+}
+.mw-ui-icon-help-invert:after, .mw-ui-icon-help-invert:before {
+       ...
+}
+.mw-ui-icon-bold:after, .mw-ui-icon-bold:before {
+       ...
+}
+.mw-ui-icon-bold-invert:after, .mw-ui-icon-bold-invert:before {
+       ...
 }',
                        ),
                );