]> _ Git - cubedesigners-v7.git/commitdiff
Remove FastClick, replace unsupported dropdown library and fix PHP 7.4 warnings and...
authorstephen@cubedesigners.com <stephen@cubedesigners.com@f5622870-0f3c-0410-866d-9cb505b7a8ef>
Tue, 7 Jan 2020 18:10:37 +0000 (18:10 +0000)
committerstephen@cubedesigners.com <stephen@cubedesigners.com@f5622870-0f3c-0410-866d-9cb505b7a8ef>
Tue, 7 Jan 2020 18:10:37 +0000 (18:10 +0000)
framework/application/Bootstrap.php
framework/application/Cubedesigners/Util.php
framework/application/layouts/scripts/layout.phtml
framework/application/views/helpers/CasestudiesDetail.php
framework/application/views/scripts/templates/casestudies.phtml
framework/application/views/scripts/templates/expertise.phtml
js/casestudies.js
js/selectric.js [new file with mode: 0644]
less/casestudies.less
less/contact.less
less/selectric.less [new file with mode: 0644]

index 631f0cf32b2712440e8fdd8bc4a72bc70bf4ef7f..855cb3687cedbf1264ebdd505ef04c57b99756cc 100644 (file)
@@ -11,8 +11,8 @@ class Bootstrap extends CubeIT_Bootstrap {
                $this->bootstrap('scripts');
        }
 
-       protected function _initRouter($initCms = true) {
-               $router = parent::_initRouter($initCms);
+       protected function _initRouter($initCms = true, $standard = true) {
+               $router = parent::_initRouter($initCms, $standard);
                $router->addStandardRoute('rss');
                return $router;
        }
index 7526c2bf85bb260aa83ae36e4e05b1932d83ccd5..9cde3039d74b91f86e0a0e437a8691bd76767123 100644 (file)
@@ -6,9 +6,11 @@ class Cubedesigners_Util {
 
        public static function getCategoryById($id, $locale) {
                self::_getCategories();
-               if (!isset(self::$_categories[$id])) {
+
+               if (is_array($id) || !isset(self::$_categories[$id])) {
                        return;
                }
+
                $res = CubeIT_Util_Cms::unserialize(self::$_categories[$id]->getName(), $locale);
                return $res;
        }
index 465c950b0a6d5b877b5fb95cc269ea9745c0bd42..bcb4cd3c09428eecf3796414d867e187c41ac258 100644 (file)
@@ -16,7 +16,7 @@ $fonts = [
 //$fonts = array('custom' => array('families' => array('Roboto Condensed'), 'urls' => array('/css/fonts/robotocondensed.css')));
 
 $this->headScript()->addWebFont($fonts);
-$this->headScript()->addFastclick();
+//$this->headScript()->addFastclick(); // Removed because it was causing errors on Android and is probably no longer needed for modern devices
 $this->headScript()->addWOW(); // For reveal on scroll animations
 
 if ($this->acl()->isAllowed('edition')) {
index 8e1a790b2a73d21328a04ae6c24de3c417f70afd..cb4e917f276ded9eb077f1767f47a09799ef3e2a 100644 (file)
@@ -62,7 +62,7 @@ class Cubedesigners_View_Helper_CasestudiesDetail extends CubeIT_View_Helper_Abs
 
                        // fb($bloc);
                        $margin = '';
-                       if ($bloc->margin != '') {
+                       if (isset($bloc->margin) && $bloc->margin != '') {
                 // Calculate margin as a percentage for responsive design
                 // Note: margin is based on max-width of images (1200px) because this is how CSS handles % margins, even for margin-top
                 $variableTopMargin = $bloc->margin / 1200 * 100;
@@ -70,7 +70,7 @@ class Cubedesigners_View_Helper_CasestudiesDetail extends CubeIT_View_Helper_Abs
                        }
 
                        $zindex = '';
-                       if ($bloc->zindex != 'default') {
+                       if (isset($bloc->zindex) && $bloc->zindex != 'default') {
                                $zindex = 'z-index:' . $bloc->zindex . ';';
                        }
 
index 46bf1b6f7dde15e4a2bbcea4892a8f9517fd949d..53b9fe2adec15c2b13552d7655c53752f0773949 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-$this->headScript()->addScriptAndStyle('fancy-select');
+$this->headScript()->addScriptAndStyle('selectric');
 $this->headScript()->addScriptAndStyle('casestudies');
 ?>
 
index 244221c455aa7e4e62bd1be40df717fa5ba49fca..75b5583ae7e3954a5b119b7dd957b33e0f8266a9 100644 (file)
@@ -1,7 +1,6 @@
 <?php
 
 $this->headScript()->addScriptAndStyle('expertises');
-$this->headScript()->addScriptAndStyle('fancy-select');
 echo $this->twocols();
 
 if(!empty($this->citation)) {
index 6123d261d2c9111bd8f1b79a14fc19b94fd162b5..21d4a693c5dd85f2d406f54800419c8be5c16425 100644 (file)
@@ -1,9 +1,14 @@
 TO_LOAD_ONCE[TO_LOAD_ONCE.length] = 'load_casestudies();';
 
 function load_casestudies() {
-       $('#casestudies-list-filter').fancySelect().on('change.fs', function() {
-    $(this).trigger('change.$');
-  }); // trigger the DOM's change event when changing FancySelect
+  $('#casestudies-list-filter').selectric({
+    maxHeight: 450,
+    arrowButtonMarkup: '', // Disable dropdown element since we are using an SVG in the background
+    disableOnMobile: true,
+    onChange: function(element) {
+      $(element).change(); // Trigger change on select box so isotope will update (see isotope-select.js)
+    },
+  });
 }
 
 
diff --git a/js/selectric.js b/js/selectric.js
new file mode 100644 (file)
index 0000000..73ec143
--- /dev/null
@@ -0,0 +1,1127 @@
+/*!
+ *         ,/
+ *       ,'/
+ *     ,' /
+ *   ,'  /_____,
+ * .'____    ,'
+ *      /  ,'
+ *     / ,'
+ *    /,'
+ *   /'
+ *
+ * Selectric ϟ v1.13.0 (Aug 22 2017) - http://lcdsantos.github.io/jQuery-Selectric/
+ *
+ * Copyright (c) 2017 Leonardo Santos; MIT License
+ *
+ */
+
+(function(factory) {
+  /* global define */
+  /* istanbul ignore next */
+  if ( typeof define === 'function' && define.amd ) {
+    define(['jquery'], factory);
+  } else if ( typeof module === 'object' && module.exports ) {
+    // Node/CommonJS
+    module.exports = function( root, jQuery ) {
+      if ( jQuery === undefined ) {
+        if ( typeof window !== 'undefined' ) {
+          jQuery = require('jquery');
+        } else {
+          jQuery = require('jquery')(root);
+        }
+      }
+      factory(jQuery);
+      return jQuery;
+    };
+  } else {
+    // Browser globals
+    factory(jQuery);
+  }
+}(function($) {
+  'use strict';
+
+  var $doc = $(document);
+  var $win = $(window);
+
+  var pluginName = 'selectric';
+  var classList = 'Input Items Open Disabled TempShow HideSelect Wrapper Focus Hover Responsive Above Below Scroll Group GroupLabel';
+  var eventNamespaceSuffix = '.sl';
+
+  var chars = ['a', 'e', 'i', 'o', 'u', 'n', 'c', 'y'];
+  var diacritics = [
+    /[\xE0-\xE5]/g, // a
+    /[\xE8-\xEB]/g, // e
+    /[\xEC-\xEF]/g, // i
+    /[\xF2-\xF6]/g, // o
+    /[\xF9-\xFC]/g, // u
+    /[\xF1]/g,      // n
+    /[\xE7]/g,      // c
+    /[\xFD-\xFF]/g  // y
+  ];
+
+  /**
+   * Create an instance of Selectric
+   *
+   * @constructor
+   * @param {Node} element - The &lt;select&gt; element
+   * @param {object}  opts - Options
+   */
+  var Selectric = function(element, opts) {
+    var _this = this;
+
+    _this.element = element;
+    _this.$element = $(element);
+
+    _this.state = {
+      multiple       : !!_this.$element.attr('multiple'),
+      enabled        : false,
+      opened         : false,
+      currValue      : -1,
+      selectedIdx    : -1,
+      highlightedIdx : -1
+    };
+
+    _this.eventTriggers = {
+      open    : _this.open,
+      close   : _this.close,
+      destroy : _this.destroy,
+      refresh : _this.refresh,
+      init    : _this.init
+    };
+
+    _this.init(opts);
+  };
+
+  Selectric.prototype = {
+    utils: {
+      /**
+       * Detect mobile browser
+       *
+       * @return {boolean}
+       */
+      isMobile: function() {
+        return /android|ip(hone|od|ad)/i.test(navigator.userAgent);
+      },
+
+      /**
+       * Escape especial characters in string (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)
+       *
+       * @param  {string} str - The string to be escaped
+       * @return {string}       The string with the special characters escaped
+       */
+      escapeRegExp: function(str) {
+        return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+      },
+
+      /**
+       * Replace diacritics
+       *
+       * @param  {string} str - The string to replace the diacritics
+       * @return {string}       The string with diacritics replaced with ascii characters
+       */
+      replaceDiacritics: function(str) {
+        var k = diacritics.length;
+
+        while (k--) {
+          str = str.toLowerCase().replace(diacritics[k], chars[k]);
+        }
+
+        return str;
+      },
+
+      /**
+       * Format string
+       * https://gist.github.com/atesgoral/984375
+       *
+       * @param  {string} f - String to be formated
+       * @return {string}     String formated
+       */
+      format: function(f) {
+        var a = arguments; // store outer arguments
+        return ('' + f) // force format specifier to String
+          .replace( // replace tokens in format specifier
+            /\{(?:(\d+)|(\w+))\}/g, // match {token} references
+            function(
+              s, // the matched string (ignored)
+              i, // an argument index
+              p // a property name
+            ) {
+              return p && a[1] // if property name and first argument exist
+                ? a[1][p] // return property from first argument
+                : a[i]; // assume argument index and return i-th argument
+            });
+      },
+
+      /**
+       * Get the next enabled item in the options list.
+       *
+       * @param  {object} selectItems - The options object.
+       * @param  {number}    selected - Index of the currently selected option.
+       * @return {object}               The next enabled item.
+       */
+      nextEnabledItem: function(selectItems, selected) {
+        while ( selectItems[ selected = (selected + 1) % selectItems.length ].disabled ) {
+          // empty
+        }
+        return selected;
+      },
+
+      /**
+       * Get the previous enabled item in the options list.
+       *
+       * @param  {object} selectItems - The options object.
+       * @param  {number}    selected - Index of the currently selected option.
+       * @return {object}               The previous enabled item.
+       */
+      previousEnabledItem: function(selectItems, selected) {
+        while ( selectItems[ selected = (selected > 0 ? selected : selectItems.length) - 1 ].disabled ) {
+          // empty
+        }
+        return selected;
+      },
+
+      /**
+       * Transform camelCase string to dash-case.
+       *
+       * @param  {string} str - The camelCased string.
+       * @return {string}       The string transformed to dash-case.
+       */
+      toDash: function(str) {
+        return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
+      },
+
+      /**
+       * Calls the events registered with function name.
+       *
+       * @param {string}    fn - The name of the function.
+       * @param {number} scope - Scope that should be set on the function.
+       */
+      triggerCallback: function(fn, scope) {
+        var elm = scope.element;
+        var func = scope.options['on' + fn];
+        var args = [elm].concat([].slice.call(arguments).slice(1));
+
+        if ( $.isFunction(func) ) {
+          func.apply(elm, args);
+        }
+
+        $(elm).trigger(pluginName + '-' + this.toDash(fn), args);
+      },
+
+      /**
+       * Transform array list to concatenated string and remove empty values
+       * @param  {array} arr - Class list
+       * @return {string}      Concatenated string
+       */
+      arrayToClassname: function(arr) {
+        var newArr = $.grep(arr, function(item) {
+          return !!item;
+        });
+
+        return $.trim(newArr.join(' '));
+      }
+    },
+
+    /** Initializes */
+    init: function(opts) {
+      var _this = this;
+
+      // Set options
+      _this.options = $.extend(true, {}, $.fn[pluginName].defaults, _this.options, opts);
+
+      _this.utils.triggerCallback('BeforeInit', _this);
+
+      // Preserve data
+      _this.destroy(true);
+
+      // Disable on mobile browsers
+      if ( _this.options.disableOnMobile && _this.utils.isMobile() ) {
+        _this.disableOnMobile = true;
+        return;
+      }
+
+      // Get classes
+      _this.classes = _this.getClassNames();
+
+      // Create elements
+      var input              = $('<input/>', { 'class': _this.classes.input, 'readonly': _this.utils.isMobile() });
+      var items              = $('<div/>',   { 'class': _this.classes.items, 'tabindex': -1 });
+      var itemsScroll        = $('<div/>',   { 'class': _this.classes.scroll });
+      var wrapper            = $('<div/>',   { 'class': _this.classes.prefix, 'html': _this.options.arrowButtonMarkup });
+      var label              = $('<span/>',  { 'class': 'label' });
+      var outerWrapper       = _this.$element.wrap('<div/>').parent().append(wrapper.prepend(label), items, input);
+      var hideSelectWrapper  = $('<div/>',   { 'class': _this.classes.hideselect });
+
+      _this.elements = {
+        input        : input,
+        items        : items,
+        itemsScroll  : itemsScroll,
+        wrapper      : wrapper,
+        label        : label,
+        outerWrapper : outerWrapper
+      };
+
+      if ( _this.options.nativeOnMobile && _this.utils.isMobile() ) {
+        _this.elements.input = undefined;
+        hideSelectWrapper.addClass(_this.classes.prefix + '-is-native');
+
+        _this.$element.on('change', function() {
+          _this.refresh();
+        });
+      }
+
+      _this.$element
+        .on(_this.eventTriggers)
+        .wrap(hideSelectWrapper);
+
+      _this.originalTabindex = _this.$element.prop('tabindex');
+      _this.$element.prop('tabindex', -1);
+
+      _this.populate();
+      _this.activate();
+
+      _this.utils.triggerCallback('Init', _this);
+    },
+
+    /** Activates the plugin */
+    activate: function() {
+      var _this = this;
+      var hiddenChildren = _this.elements.items.closest(':visible').children(':hidden').addClass(_this.classes.tempshow);
+      var originalWidth = _this.$element.width();
+
+      hiddenChildren.removeClass(_this.classes.tempshow);
+
+      _this.utils.triggerCallback('BeforeActivate', _this);
+
+      _this.elements.outerWrapper.prop('class',
+        _this.utils.arrayToClassname([
+          _this.classes.wrapper,
+          _this.$element.prop('class').replace(/\S+/g, _this.classes.prefix + '-$&'),
+          _this.options.responsive ? _this.classes.responsive : ''
+        ])
+      );
+
+      if ( _this.options.inheritOriginalWidth && originalWidth > 0 ) {
+        _this.elements.outerWrapper.width(originalWidth);
+      }
+
+      _this.unbindEvents();
+
+      if ( !_this.$element.prop('disabled') ) {
+        _this.state.enabled = true;
+
+        // Not disabled, so... Removing disabled class
+        _this.elements.outerWrapper.removeClass(_this.classes.disabled);
+
+        // Remove styles from items box
+        // Fix incorrect height when refreshed is triggered with fewer options
+        _this.$li = _this.elements.items.removeAttr('style').find('li');
+
+        _this.bindEvents();
+      } else {
+        _this.elements.outerWrapper.addClass(_this.classes.disabled);
+
+        if ( _this.elements.input ) {
+          _this.elements.input.prop('disabled', true);
+        }
+      }
+
+      _this.utils.triggerCallback('Activate', _this);
+    },
+
+    /**
+     * Generate classNames for elements
+     *
+     * @return {object} Classes object
+     */
+    getClassNames: function() {
+      var _this = this;
+      var customClass = _this.options.customClass;
+      var classesObj = {};
+
+      $.each(classList.split(' '), function(i, currClass) {
+        var c = customClass.prefix + currClass;
+        classesObj[currClass.toLowerCase()] = customClass.camelCase ? c : _this.utils.toDash(c);
+      });
+
+      classesObj.prefix = customClass.prefix;
+
+      return classesObj;
+    },
+
+    /** Set the label text */
+    setLabel: function() {
+      var _this = this;
+      var labelBuilder = _this.options.labelBuilder;
+
+      if ( _this.state.multiple ) {
+        // Make sure currentValues is an array
+        var currentValues = $.isArray(_this.state.currValue) ? _this.state.currValue : [_this.state.currValue];
+        // I'm not happy with this, but currentValues can be an empty
+        // array and we need to fallback to the default option.
+        currentValues = currentValues.length === 0 ? [0] : currentValues;
+
+        var labelMarkup = $.map(currentValues, function(value) {
+          return $.grep(_this.lookupItems, function(item) {
+            return item.index === value;
+          })[0]; // we don't want nested arrays here
+        });
+
+        labelMarkup = $.grep(labelMarkup, function(item) {
+          // Hide default (please choose) if more then one element were selected.
+          // If no option value were given value is set to option text by default
+          if ( labelMarkup.length > 1 || labelMarkup.length === 0 ) {
+            return $.trim(item.value) !== '';
+          }
+          return item;
+        });
+
+        labelMarkup = $.map(labelMarkup, function(item) {
+          return $.isFunction(labelBuilder)
+            ? labelBuilder(item)
+            : _this.utils.format(labelBuilder, item);
+        });
+
+        // Limit the amount of selected values shown in label
+        if ( _this.options.multiple.maxLabelEntries ) {
+          if ( labelMarkup.length >= _this.options.multiple.maxLabelEntries + 1 ) {
+            labelMarkup = labelMarkup.slice(0, _this.options.multiple.maxLabelEntries);
+            labelMarkup.push(
+              $.isFunction(labelBuilder)
+                ? labelBuilder({ text: '...' })
+                : _this.utils.format(labelBuilder, { text: '...' }));
+          } else {
+            labelMarkup.slice(labelMarkup.length - 1);
+          }
+        }
+        _this.elements.label.html(labelMarkup.join(_this.options.multiple.separator));
+
+      } else {
+        var currItem = _this.lookupItems[_this.state.currValue];
+
+        _this.elements.label.html(
+          $.isFunction(labelBuilder)
+            ? labelBuilder(currItem)
+            : _this.utils.format(labelBuilder, currItem)
+        );
+      }
+    },
+
+    /** Get and save the available options */
+    populate: function() {
+      var _this = this;
+      var $options = _this.$element.children();
+      var $justOptions = _this.$element.find('option');
+      var $selected = $justOptions.filter(':selected');
+      var selectedIndex = $justOptions.index($selected);
+      var currIndex = 0;
+      var emptyValue = (_this.state.multiple ? [] : 0);
+
+      if ( $selected.length > 1 && _this.state.multiple ) {
+        selectedIndex = [];
+        $selected.each(function() {
+          selectedIndex.push($(this).index());
+        });
+      }
+
+      _this.state.currValue = (~selectedIndex ? selectedIndex : emptyValue);
+      _this.state.selectedIdx = _this.state.currValue;
+      _this.state.highlightedIdx = _this.state.currValue;
+      _this.items = [];
+      _this.lookupItems = [];
+
+      if ( $options.length ) {
+        // Build options markup
+        $options.each(function(i) {
+          var $elm = $(this);
+
+          if ( $elm.is('optgroup') ) {
+
+            var optionsGroup = {
+              element       : $elm,
+              label         : $elm.prop('label'),
+              groupDisabled : $elm.prop('disabled'),
+              items         : []
+            };
+
+            $elm.children().each(function(i) {
+              var $elm = $(this);
+
+              optionsGroup.items[i] = _this.getItemData(currIndex, $elm, optionsGroup.groupDisabled || $elm.prop('disabled'));
+
+              _this.lookupItems[currIndex] = optionsGroup.items[i];
+
+              currIndex++;
+            });
+
+            _this.items[i] = optionsGroup;
+
+          } else {
+
+            _this.items[i] = _this.getItemData(currIndex, $elm, $elm.prop('disabled'));
+
+            _this.lookupItems[currIndex] = _this.items[i];
+
+            currIndex++;
+
+          }
+        });
+
+        _this.setLabel();
+        _this.elements.items.append( _this.elements.itemsScroll.html( _this.getItemsMarkup(_this.items) ) );
+      }
+    },
+
+    /**
+     * Generate items object data
+     * @param  {integer} index      - Current item index
+     * @param  {node}    $elm       - Current element node
+     * @param  {boolean} isDisabled - Current element disabled state
+     * @return {object}               Item object
+     */
+    getItemData: function(index, $elm, isDisabled) {
+      var _this = this;
+
+      return {
+        index     : index,
+        element   : $elm,
+        value     : $elm.val(),
+        className : $elm.prop('class'),
+        text      : $elm.html(),
+        slug      : $.trim(_this.utils.replaceDiacritics($elm.html())),
+        alt       : $elm.attr('data-alt'),
+        selected  : $elm.prop('selected'),
+        disabled  : isDisabled
+      };
+    },
+
+    /**
+     * Generate options markup
+     *
+     * @param  {object} items - Object containing all available options
+     * @return {string}         HTML for the options box
+     */
+    getItemsMarkup: function(items) {
+      var _this = this;
+      var markup = '<ul>';
+
+      if ( $.isFunction(_this.options.listBuilder) && _this.options.listBuilder ) {
+        items = _this.options.listBuilder(items);
+      }
+
+      $.each(items, function(i, elm) {
+        if ( elm.label !== undefined ) {
+
+          markup += _this.utils.format('<ul class="{1}"><li class="{2}">{3}</li>',
+            _this.utils.arrayToClassname([
+              _this.classes.group,
+              elm.groupDisabled ? 'disabled' : '',
+              elm.element.prop('class')
+            ]),
+            _this.classes.grouplabel,
+            elm.element.prop('label')
+          );
+
+          $.each(elm.items, function(i, elm) {
+            markup += _this.getItemMarkup(elm.index, elm);
+          });
+
+          markup += '</ul>';
+
+        } else {
+
+          markup += _this.getItemMarkup(elm.index, elm);
+
+        }
+      });
+
+      return markup + '</ul>';
+    },
+
+    /**
+     * Generate every option markup
+     *
+     * @param  {number} index    - Index of current item
+     * @param  {object} itemData - Current item
+     * @return {string}            HTML for the option
+     */
+    getItemMarkup: function(index, itemData) {
+      var _this = this;
+      var itemBuilder = _this.options.optionsItemBuilder;
+      // limit access to item data to provide a simple interface
+      // to most relevant options.
+      var filteredItemData = {
+        value: itemData.value,
+        text : itemData.text,
+        slug : itemData.slug,
+        index: itemData.index
+      };
+
+      return _this.utils.format('<li data-index="{1}" class="{2}">{3}</li>',
+        index,
+        _this.utils.arrayToClassname([
+          itemData.className,
+          index === _this.items.length - 1  ? 'last'     : '',
+          itemData.disabled                 ? 'disabled' : '',
+          itemData.selected                 ? 'selected' : ''
+        ]),
+        $.isFunction(itemBuilder)
+          ? _this.utils.format(itemBuilder(itemData, this.$element, index), itemData)
+          : _this.utils.format(itemBuilder, filteredItemData)
+      );
+    },
+
+    /** Remove events on the elements */
+    unbindEvents: function() {
+      var _this = this;
+
+      _this.elements.wrapper
+        .add(_this.$element)
+        .add(_this.elements.outerWrapper)
+        .add(_this.elements.input)
+        .off(eventNamespaceSuffix);
+    },
+
+    /** Bind events on the elements */
+    bindEvents: function() {
+      var _this = this;
+
+      _this.elements.outerWrapper.on('mouseenter' + eventNamespaceSuffix + ' mouseleave' + eventNamespaceSuffix, function(e) {
+        $(this).toggleClass(_this.classes.hover, e.type === 'mouseenter');
+
+        // Delay close effect when openOnHover is true
+        if ( _this.options.openOnHover ) {
+          clearTimeout(_this.closeTimer);
+
+          if ( e.type === 'mouseleave' ) {
+            _this.closeTimer = setTimeout($.proxy(_this.close, _this), _this.options.hoverIntentTimeout);
+          } else {
+            _this.open();
+          }
+        }
+      });
+
+      // Toggle open/close
+      _this.elements.wrapper.on('click' + eventNamespaceSuffix, function(e) {
+        _this.state.opened ? _this.close() : _this.open(e);
+      });
+
+      // Translate original element focus event to dummy input.
+      // Disabled on mobile devices because the default option list isn't
+      // shown due the fact that hidden input gets focused
+      if ( !(_this.options.nativeOnMobile && _this.utils.isMobile()) ) {
+        _this.$element.on('focus' + eventNamespaceSuffix, function() {
+          _this.elements.input.focus();
+        });
+
+        _this.elements.input
+          .prop({ tabindex: _this.originalTabindex, disabled: false })
+          .on('keydown' + eventNamespaceSuffix, $.proxy(_this.handleKeys, _this))
+          .on('focusin' + eventNamespaceSuffix, function(e) {
+            _this.elements.outerWrapper.addClass(_this.classes.focus);
+
+            // Prevent the flicker when focusing out and back again in the browser window
+            _this.elements.input.one('blur', function() {
+              _this.elements.input.blur();
+            });
+
+            if ( _this.options.openOnFocus && !_this.state.opened ) {
+              _this.open(e);
+            }
+          })
+          .on('focusout' + eventNamespaceSuffix, function() {
+            _this.elements.outerWrapper.removeClass(_this.classes.focus);
+          })
+          .on('input propertychange', function() {
+            var val = _this.elements.input.val();
+            var searchRegExp = new RegExp('^' + _this.utils.escapeRegExp(val), 'i');
+
+            // Clear search
+            clearTimeout(_this.resetStr);
+            _this.resetStr = setTimeout(function() {
+              _this.elements.input.val('');
+            }, _this.options.keySearchTimeout);
+
+            if ( val.length ) {
+              // Search in select options
+              $.each(_this.items, function(i, elm) {
+                if (elm.disabled) {
+                  return;
+                }
+                if (searchRegExp.test(elm.text) || searchRegExp.test(elm.slug)) {
+                  _this.highlight(i);
+                  return;
+                }
+                if (!elm.alt) {
+                  return;
+                }
+                var altItems = elm.alt.split('|');
+                for (var ai = 0; ai < altItems.length; ai++) {
+                  if (!altItems[ai]) {
+                    break;
+                  }
+                  if (searchRegExp.test(altItems[ai].trim())) {
+                    _this.highlight(i);
+                    return;
+                  }
+                }
+              });
+            }
+          });
+      }
+
+      _this.$li.on({
+        // Prevent <input> blur on Chrome
+        mousedown: function(e) {
+          e.preventDefault();
+          e.stopPropagation();
+        },
+        click: function() {
+          _this.select($(this).data('index'));
+
+          // Chrome doesn't close options box if select is wrapped with a label
+          // We need to 'return false' to avoid that
+          return false;
+        }
+      });
+    },
+
+    /**
+     * Behavior when keyboard keys is pressed
+     *
+     * @param {object} e - Event object
+     */
+    handleKeys: function(e) {
+      var _this = this;
+      var key = e.which;
+      var keys = _this.options.keys;
+
+      var isPrevKey = $.inArray(key, keys.previous) > -1;
+      var isNextKey = $.inArray(key, keys.next) > -1;
+      var isSelectKey = $.inArray(key, keys.select) > -1;
+      var isOpenKey = $.inArray(key, keys.open) > -1;
+      var idx = _this.state.highlightedIdx;
+      var isFirstOrLastItem = (isPrevKey && idx === 0) || (isNextKey && (idx + 1) === _this.items.length);
+      var goToItem = 0;
+
+      // Enter / Space
+      if ( key === 13 || key === 32 ) {
+        e.preventDefault();
+      }
+
+      // If it's a directional key
+      if ( isPrevKey || isNextKey ) {
+        if ( !_this.options.allowWrap && isFirstOrLastItem ) {
+          return;
+        }
+
+        if ( isPrevKey ) {
+          goToItem = _this.utils.previousEnabledItem(_this.lookupItems, idx);
+        }
+
+        if ( isNextKey ) {
+          goToItem = _this.utils.nextEnabledItem(_this.lookupItems, idx);
+        }
+
+        _this.highlight(goToItem);
+      }
+
+      // Tab / Enter / ESC
+      if ( isSelectKey && _this.state.opened ) {
+        _this.select(idx);
+
+        if ( !_this.state.multiple || !_this.options.multiple.keepMenuOpen ) {
+          _this.close();
+        }
+
+        return;
+      }
+
+      // Space / Enter / Left / Up / Right / Down
+      if ( isOpenKey && !_this.state.opened ) {
+        _this.open();
+      }
+    },
+
+    /** Update the items object */
+    refresh: function() {
+      var _this = this;
+
+      _this.populate();
+      _this.activate();
+      _this.utils.triggerCallback('Refresh', _this);
+    },
+
+    /** Set options box width/height */
+    setOptionsDimensions: function() {
+      var _this = this;
+
+      // Calculate options box height
+      // Set a temporary class on the hidden parent of the element
+      var hiddenChildren = _this.elements.items.closest(':visible').children(':hidden').addClass(_this.classes.tempshow);
+      var maxHeight = _this.options.maxHeight;
+      var itemsWidth = _this.elements.items.outerWidth();
+      var wrapperWidth = _this.elements.wrapper.outerWidth() - (itemsWidth - _this.elements.items.width());
+
+      // Set the dimensions, minimum is wrapper width, expand for long items if option is true
+      if ( !_this.options.expandToItemText || wrapperWidth > itemsWidth ) {
+        _this.finalWidth = wrapperWidth;
+      } else {
+        // Make sure the scrollbar width is included
+        _this.elements.items.css('overflow', 'scroll');
+
+        // Set a really long width for _this.elements.outerWrapper
+        _this.elements.outerWrapper.width(9e4);
+        _this.finalWidth = _this.elements.items.width();
+        // Set scroll bar to auto
+        _this.elements.items.css('overflow', '');
+        _this.elements.outerWrapper.width('');
+      }
+
+      _this.elements.items.width(_this.finalWidth).height() > maxHeight && _this.elements.items.height(maxHeight);
+
+      // Remove the temporary class
+      hiddenChildren.removeClass(_this.classes.tempshow);
+    },
+
+    /** Detect if the options box is inside the window */
+    isInViewport: function() {
+      var _this = this;
+
+      if (_this.options.forceRenderAbove === true) {
+        _this.elements.outerWrapper.addClass(_this.classes.above);
+      } else if (_this.options.forceRenderBelow === true) {
+        _this.elements.outerWrapper.addClass(_this.classes.below);
+      } else {
+        var scrollTop = $win.scrollTop();
+        var winHeight = $win.height();
+        var uiPosX = _this.elements.outerWrapper.offset().top;
+        var uiHeight = _this.elements.outerWrapper.outerHeight();
+
+        var fitsDown = (uiPosX + uiHeight + _this.itemsHeight) <= (scrollTop + winHeight);
+        var fitsAbove = (uiPosX - _this.itemsHeight) > scrollTop;
+
+        // If it does not fit below, only render it
+        // above it fit's there.
+        // It's acceptable that the user needs to
+        // scroll the viewport to see the cut off UI
+        var renderAbove = !fitsDown && fitsAbove;
+        var renderBelow = !renderAbove;
+
+        _this.elements.outerWrapper.toggleClass(_this.classes.above, renderAbove);
+        _this.elements.outerWrapper.toggleClass(_this.classes.below, renderBelow);
+      }
+    },
+
+    /**
+     * Detect if currently selected option is visible and scroll the options box to show it
+     *
+     * @param {Number|Array} index - Index of the selected items
+     */
+    detectItemVisibility: function(index) {
+      var _this = this;
+      var $filteredLi = _this.$li.filter('[data-index]');
+
+      if ( _this.state.multiple ) {
+        // If index is an array, we can assume a multiple select and we
+        // want to scroll to the uppermost selected item!
+        // Math.min.apply(Math, index) returns the lowest entry in an Array.
+        index = ($.isArray(index) && index.length === 0) ? 0 : index;
+        index = $.isArray(index) ? Math.min.apply(Math, index) : index;
+      }
+
+      var liHeight = $filteredLi.eq(index).outerHeight();
+      var liTop = $filteredLi[index].offsetTop;
+      var itemsScrollTop = _this.elements.itemsScroll.scrollTop();
+      var scrollT = liTop + liHeight * 2;
+
+      _this.elements.itemsScroll.scrollTop(
+        scrollT > itemsScrollTop + _this.itemsHeight ? scrollT - _this.itemsHeight :
+          liTop - liHeight < itemsScrollTop ? liTop - liHeight :
+            itemsScrollTop
+      );
+    },
+
+    /**
+     * Open the select options box
+     *
+     * @param {Event} e - Event
+     */
+    open: function(e) {
+      var _this = this;
+
+      if ( _this.options.nativeOnMobile && _this.utils.isMobile()) {
+        return false;
+      }
+
+      _this.utils.triggerCallback('BeforeOpen', _this);
+
+      if ( e ) {
+        e.preventDefault();
+        if (_this.options.stopPropagation) {
+          e.stopPropagation();
+        }
+      }
+
+      if ( _this.state.enabled ) {
+        _this.setOptionsDimensions();
+
+        // Find any other opened instances of select and close it
+        $('.' + _this.classes.hideselect, '.' + _this.classes.open).children()[pluginName]('close');
+
+        _this.state.opened = true;
+        _this.itemsHeight = _this.elements.items.outerHeight();
+        _this.itemsInnerHeight = _this.elements.items.height();
+
+        // Toggle options box visibility
+        _this.elements.outerWrapper.addClass(_this.classes.open);
+
+        // Give dummy input focus
+        _this.elements.input.val('');
+        if ( e && e.type !== 'focusin' ) {
+          _this.elements.input.focus();
+        }
+
+        // Delayed binds events on Document to make label clicks work
+        setTimeout(function() {
+          $doc
+            .on('click' + eventNamespaceSuffix, $.proxy(_this.close, _this))
+            .on('scroll' + eventNamespaceSuffix, $.proxy(_this.isInViewport, _this));
+        }, 1);
+
+        _this.isInViewport();
+
+        // Prevent window scroll when using mouse wheel inside items box
+        if ( _this.options.preventWindowScroll ) {
+          /* istanbul ignore next */
+          $doc.on('mousewheel' + eventNamespaceSuffix + ' DOMMouseScroll' + eventNamespaceSuffix, '.' + _this.classes.scroll, function(e) {
+            var orgEvent = e.originalEvent;
+            var scrollTop = $(this).scrollTop();
+            var deltaY = 0;
+
+            if ( 'detail'      in orgEvent ) { deltaY = orgEvent.detail * -1; }
+            if ( 'wheelDelta'  in orgEvent ) { deltaY = orgEvent.wheelDelta;  }
+            if ( 'wheelDeltaY' in orgEvent ) { deltaY = orgEvent.wheelDeltaY; }
+            if ( 'deltaY'      in orgEvent ) { deltaY = orgEvent.deltaY * -1; }
+
+            if ( scrollTop === (this.scrollHeight - _this.itemsInnerHeight) && deltaY < 0 || scrollTop === 0 && deltaY > 0 ) {
+              e.preventDefault();
+            }
+          });
+        }
+
+        _this.detectItemVisibility(_this.state.selectedIdx);
+
+        _this.highlight(_this.state.multiple ? -1 : _this.state.selectedIdx);
+
+        _this.utils.triggerCallback('Open', _this);
+      }
+    },
+
+    /** Close the select options box */
+    close: function() {
+      var _this = this;
+
+      _this.utils.triggerCallback('BeforeClose', _this);
+
+      // Remove custom events on document
+      $doc.off(eventNamespaceSuffix);
+
+      // Remove visible class to hide options box
+      _this.elements.outerWrapper.removeClass(_this.classes.open);
+
+      _this.state.opened = false;
+
+      _this.utils.triggerCallback('Close', _this);
+    },
+
+    /** Select current option and change the label */
+    change: function() {
+      var _this = this;
+
+      _this.utils.triggerCallback('BeforeChange', _this);
+
+      if ( _this.state.multiple ) {
+        // Reset old selected
+        $.each(_this.lookupItems, function(idx) {
+          _this.lookupItems[idx].selected = false;
+          _this.$element.find('option').prop('selected', false);
+        });
+
+        // Set new selected
+        $.each(_this.state.selectedIdx, function(idx, value) {
+          _this.lookupItems[value].selected = true;
+          _this.$element.find('option').eq(value).prop('selected', true);
+        });
+
+        _this.state.currValue = _this.state.selectedIdx;
+
+        _this.setLabel();
+
+        _this.utils.triggerCallback('Change', _this);
+      } else if ( _this.state.currValue !== _this.state.selectedIdx ) {
+        // Apply changed value to original select
+        _this.$element
+          .prop('selectedIndex', _this.state.currValue = _this.state.selectedIdx)
+          .data('value', _this.lookupItems[_this.state.selectedIdx].text);
+
+        // Change label text
+        _this.setLabel();
+
+        _this.utils.triggerCallback('Change', _this);
+      }
+    },
+
+    /**
+     * Highlight option
+     * @param {number} index - Index of the options that will be highlighted
+     */
+    highlight: function(index) {
+      var _this = this;
+      var $filteredLi = _this.$li.filter('[data-index]').removeClass('highlighted');
+
+      _this.utils.triggerCallback('BeforeHighlight', _this);
+
+      // Parameter index is required and should not be a disabled item
+      if ( index === undefined || index === -1 || _this.lookupItems[index].disabled ) {
+        return;
+      }
+
+      $filteredLi
+        .eq(_this.state.highlightedIdx = index)
+        .addClass('highlighted');
+
+      _this.detectItemVisibility(index);
+
+      _this.utils.triggerCallback('Highlight', _this);
+    },
+
+    /**
+     * Select option
+     *
+     * @param {number} index - Index of the option that will be selected
+     */
+    select: function(index) {
+      var _this = this;
+      var $filteredLi = _this.$li.filter('[data-index]');
+
+      _this.utils.triggerCallback('BeforeSelect', _this, index);
+
+      // Parameter index is required and should not be a disabled item
+      if ( index === undefined || index === -1 || _this.lookupItems[index].disabled ) {
+        return;
+      }
+
+      if ( _this.state.multiple ) {
+        // Make sure selectedIdx is an array
+        _this.state.selectedIdx = $.isArray(_this.state.selectedIdx) ? _this.state.selectedIdx : [_this.state.selectedIdx];
+
+        var hasSelectedIndex = $.inArray(index, _this.state.selectedIdx);
+        if ( hasSelectedIndex !== -1 ) {
+          _this.state.selectedIdx.splice(hasSelectedIndex, 1);
+        } else {
+          _this.state.selectedIdx.push(index);
+        }
+
+        $filteredLi
+          .removeClass('selected')
+          .filter(function(index) {
+            return $.inArray(index, _this.state.selectedIdx) !== -1;
+          })
+          .addClass('selected');
+      } else {
+        $filteredLi
+          .removeClass('selected')
+          .eq(_this.state.selectedIdx = index)
+          .addClass('selected');
+      }
+
+      if ( !_this.state.multiple || !_this.options.multiple.keepMenuOpen ) {
+        _this.close();
+      }
+
+      _this.change();
+
+      _this.utils.triggerCallback('Select', _this, index);
+    },
+
+    /**
+     * Unbind and remove
+     *
+     * @param {boolean} preserveData - Check if the data on the element should be removed too
+     */
+    destroy: function(preserveData) {
+      var _this = this;
+
+      if ( _this.state && _this.state.enabled ) {
+        _this.elements.items.add(_this.elements.wrapper).add(_this.elements.input).remove();
+
+        if ( !preserveData ) {
+          _this.$element.removeData(pluginName).removeData('value');
+        }
+
+        _this.$element.prop('tabindex', _this.originalTabindex).off(eventNamespaceSuffix).off(_this.eventTriggers).unwrap().unwrap();
+
+        _this.state.enabled = false;
+      }
+    }
+  };
+
+  // A really lightweight plugin wrapper around the constructor,
+  // preventing against multiple instantiations
+  $.fn[pluginName] = function(args) {
+    return this.each(function() {
+      var data = $.data(this, pluginName);
+
+      if ( data && !data.disableOnMobile ) {
+        (typeof args === 'string' && data[args]) ? data[args]() : data.init(args);
+      } else {
+        $.data(this, pluginName, new Selectric(this, args));
+      }
+    });
+  };
+
+  /**
+   * Default plugin options
+   *
+   * @type {object}
+   */
+  $.fn[pluginName].defaults = {
+    onChange             : function(elm) { $(elm).change(); },
+    maxHeight            : 300,
+    keySearchTimeout     : 500,
+    arrowButtonMarkup    : '<b class="button">&#x25be;</b>',
+    disableOnMobile      : false,
+    nativeOnMobile       : true,
+    openOnFocus          : true,
+    openOnHover          : false,
+    hoverIntentTimeout   : 500,
+    expandToItemText     : false,
+    responsive           : false,
+    preventWindowScroll  : true,
+    inheritOriginalWidth : false,
+    allowWrap            : true,
+    forceRenderAbove     : false,
+    forceRenderBelow     : false,
+    stopPropagation      : true,
+    optionsItemBuilder   : '{text}', // function(itemData, element, index)
+    labelBuilder         : '{text}', // function(currItem)
+    listBuilder          : false,    // function(items)
+    keys                 : {
+      previous : [37, 38],                 // Left / Up
+      next     : [39, 40],                 // Right / Down
+      select   : [9, 13, 27],              // Tab / Enter / Escape
+      open     : [13, 32, 37, 38, 39, 40], // Enter / Space / Left / Up / Right / Down
+      close    : [9, 27]                   // Tab / Escape
+    },
+    customClass          : {
+      prefix: pluginName,
+      camelCase: false
+    },
+    multiple              : {
+      separator: ', ',
+      keepMenuOpen: true,
+      maxLabelEntries: false
+    }
+  };
+}));
index 72905239b726214825543c0e99ecd7aec362cb74..3445ee352dde8e9054bdbbdecb854035e8e50146 100644 (file)
@@ -3,6 +3,19 @@
 .casestudies-filter {
   margin-bottom: 2em;
   text-align: right;
+  font-size: 20px;
+
+  .selectric-items {
+    width: auto !important;
+  }
+
+  select {
+    // The select box normally isn't visible but it is triggered
+    // and on iOS, if the font size is too small (below 16px after
+    // display scaling), the page will be zoomed when the select is focused
+    // Related: https://stackoverflow.com/questions/2989263/disable-auto-zoom-in-input-text-tag-safari-on-iphone
+    font-size: 32px;
+  }
 }
 
 .casestudies-list {
index 7fd04a6b7f95f2eeebe4202dd290cc181e346546..c0141875fe70ece436e26c0931cadb375e4c49e5 100644 (file)
        .contact-texte {
                background-image: url('../images/picto_footer_mail.svg');
                background-repeat: no-repeat;
-               background-position: 0px 10px;
+               background-position: 0 0;
                font-weight: 300;
                padding-left: 60px;
-               padding-bottom: 50px;
+               padding-top: 10px;
+               min-height: 50px;
+               margin-bottom: 30px;
        }
 
        .titre {
diff --git a/less/selectric.less b/less/selectric.less
new file mode 100644 (file)
index 0000000..cd1bc90
--- /dev/null
@@ -0,0 +1,175 @@
+@import "00-constants";
+
+.selectric-wrapper {
+  position: relative;
+  cursor: pointer;
+}
+
+.selectric-responsive {
+  width: 100%;
+}
+
+.selectric {
+  position: relative;
+  overflow: hidden;
+
+  .label {
+    display: inline-block;
+    background: url('data:image/svg+xml;utf8,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 14.6 8" style="enable-background:new 0 0 14.6 8;" xml:space="preserve"><path d="M14.3,0.4c-0.2-0.2-0.5-0.2-0.7,0L7.3,6.6L1.1,0.4c-0.2-0.2-0.5-0.2-0.7,0s-0.2,0.5,0,0.7L7,7.7c0,0,0,0,0,0c0.1,0.1,0.2,0.1,0.4,0.1c0,0,0,0,0,0s0,0,0,0c0.1,0,0.3,0,0.4-0.1c0,0,0,0,0,0l6.6-6.6C14.5,0.9,14.5,0.5,14.3,0.4z"/></svg>') center right no-repeat;
+    background-size: auto 0.4em;
+    cursor: pointer;
+    padding: 0 1.5em 0 0.4em;
+    line-height: 2;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    position: relative;
+    //font-size: 12px;
+    //line-height: 38px;
+    color: #444;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+  }
+
+  //.button {
+  //  display: none;
+  //}
+
+  .selectric-focus & {
+    border-color: #aaa;
+  }
+
+  .selectric-hover & {
+    border-color: #c4c4c4;
+  }
+
+}
+
+.selectric-open {
+  z-index: 9999;
+
+  .selectric {
+    border-color: #c4c4c4;
+  }
+
+  .selectric-items {
+    display: block;
+  }
+}
+
+
+.selectric-hide-select {
+  position: relative;
+  overflow: hidden;
+  //width: 0;
+  //height: 0;
+
+  &.selectric-is-native {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    z-index: 10;
+
+    select {
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      height: 100%;
+      width: 100%;
+      border: none;
+      z-index: 1;
+      box-sizing: border-box;
+      opacity: 0;
+    }
+  }
+
+  select {
+    position: absolute;
+    left: -100%;
+    font-size: 20px;
+  }
+}
+
+
+.selectric-input {
+  position: absolute !important;
+  top: 0 !important;
+  left: 0 !important;
+  overflow: hidden !important;
+  clip: rect(0, 0, 0, 0) !important;
+  margin: 0 !important;
+  padding: 0 !important;
+  width: 1px !important;
+  height: 1px !important;
+  outline: none !important;
+  border: none !important;
+  //*font: 0/0 a !important;
+  background: none !important;
+}
+
+.selectric-temp-show {
+  position: absolute !important;
+  visibility: hidden !important;
+  display: block !important;
+}
+
+/* Items box */
+.selectric-items {
+  display: none;
+  position: absolute;
+  top: 100%;
+  right: 0;
+  background: #fff;
+  z-index: -1;
+
+  .selectric-scroll {
+    height: 100%;
+    overflow: auto;
+  }
+
+  .selectric-above & {
+    top: auto;
+    bottom: 100%;
+  }
+
+  ul, li {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+    //font-size: 12px;
+    //line-height: 20px;
+    //min-height: 20px;
+  }
+
+  li {
+    display: block;
+    padding: 0.5em 2em;
+    cursor: pointer;
+
+    &.selected, &.selected.highlighted {
+      background-color: rgba(200,200,200,.35);
+
+      &:hover {
+        background-color: @yellow;
+      }
+    }
+
+    &.highlighted {
+      background-color: rgba(200,200,200,.15);
+    }
+
+    &:before {
+      display: none;
+    }
+
+    &:hover {
+      background: @yellow;
+      color: #fff;
+    }
+  }
+
+}
+