3 namespace Drupal\slick;
5 use Drupal\Component\Utility\Html;
6 use Drupal\Component\Utility\NestedArray;
7 use Drupal\Core\Cache\Cache;
8 use Drupal\slick\Entity\Slick;
9 use Drupal\blazy\BlazyManagerBase;
10 use Drupal\blazy\BlazyManagerInterface;
13 * Implements BlazyManagerInterface, SlickManagerInterface.
15 class SlickManager extends BlazyManagerBase implements BlazyManagerInterface, SlickManagerInterface {
18 * The supported skins.
22 private static $skins = [
33 * Returns the supported skins.
35 public static function getConstantSkins() {
40 * Returns slick skins registered via hook_slick_skins_info(), or defaults.
42 * @see \Drupal\blazy\BlazyManagerBase::buildSkins()
44 public function getSkins() {
45 $skins = &drupal_static(__METHOD__, NULL);
47 $methods = ['skins', 'arrows', 'dots'];
48 $skins = $this->buildSkins('slick', '\Drupal\slick\SlickSkin', $methods);
54 * Returns available slick skins by group.
56 public function getSkinsByGroup($group = '', $option = FALSE) {
57 $skins = $groups = $ungroups = [];
58 $nav_skins = in_array($group, ['arrows', 'dots']);
59 $defined_skins = $nav_skins ? $this->getSkins()[$group] : $this->getSkins()['skins'];
61 foreach ($defined_skins as $skin => $properties) {
62 $item = $option ? Html::escape($properties['name']) : $properties;
64 if (isset($properties['group'])) {
65 if ($properties['group'] != $group) {
68 $groups[$skin] = $item;
70 elseif (!$nav_skins) {
71 $ungroups[$skin] = $item;
74 $skins[$skin] = $item;
77 return $group ? array_merge($ungroups, $groups) : $skins;
81 * Implements hook_library_info_build().
83 public function libraryInfoBuild() {
84 $libraries['slick.css'] = [
85 'dependencies' => ['slick/slick'],
87 'theme' => ['/libraries/slick/slick/slick-theme.css' => []],
91 foreach (self::getConstantSkins() as $group) {
92 if ($skins = $this->getSkinsByGroup($group)) {
93 foreach ($skins as $key => $skin) {
94 $provider = isset($skin['provider']) ? $skin['provider'] : 'slick';
95 $id = $provider . '.' . $group . '.' . $key;
97 foreach (['css', 'js', 'dependencies'] as $property) {
98 if (isset($skin[$property]) && is_array($skin[$property])) {
99 $libraries[$id][$property] = $skin[$property];
112 public function attach($attach = []) {
114 'slick_css' => $this->configLoad('slick_css', 'slick.settings'),
115 'module_css' => $this->configLoad('module_css', 'slick.settings'),
118 $load = parent::attach($attach);
120 if (!empty($attach['lazy'])) {
121 $load['library'][] = 'blazy/loading';
124 // @todo: Only load slick if not static grid.
125 if (is_file('libraries/easing/jquery.easing.min.js')) {
126 $load['library'][] = 'slick/slick.easing';
129 $load['library'][] = 'slick/slick.load';
131 $components = ['colorbox', 'mousewheel'];
132 foreach ($components as $component) {
133 if (!empty($attach[$component])) {
134 $load['library'][] = 'slick/slick.' . $component;
138 if (!empty($attach['skin'])) {
139 $this->attachSkin($load, $attach);
142 // Attach default JS settings to allow responsive displays have a lookup,
143 // excluding wasted/trouble options, e.g.: PHP string vs JS object.
144 $excludes = explode(' ', 'mobileFirst appendArrows appendDots asNavFor prevArrow nextArrow respondTo');
145 $excludes = array_combine($excludes, $excludes);
146 $load['drupalSettings']['slick'] = array_diff_key(Slick::defaultSettings(), $excludes);
148 $this->moduleHandler->alter('slick_attach_load_info', $load, $attach);
153 * Provides skins only if required.
155 public function attachSkin(array &$load, $attach = []) {
156 if ($attach['slick_css']) {
157 $load['library'][] = 'slick/slick.css';
160 if ($attach['module_css']) {
161 $load['library'][] = 'slick/slick.theme';
164 if (!empty($attach['thumbnail_effect'])) {
165 $load['library'][] = 'slick/slick.thumbnail.' . $attach['thumbnail_effect'];
168 if (!empty($attach['down_arrow'])) {
169 $load['library'][] = 'slick/slick.arrow.down';
172 foreach (self::getConstantSkins() as $group) {
173 $skin = $group == 'main' ? $attach['skin'] : (isset($attach['skin_' . $group]) ? $attach['skin_' . $group] : '');
175 $skins = $this->getSkinsByGroup($group);
176 $provider = isset($skins[$skin]['provider']) ? $skins[$skin]['provider'] : 'slick';
177 $load['library'][] = 'slick/' . $provider . '.' . $group . '.' . $skin;
185 public static function slick(array $build = []) {
186 foreach (['items', 'options', 'optionset', 'settings'] as $key) {
187 $build[$key] = isset($build[$key]) ? $build[$key] : [];
190 if (empty($build['items'])) {
198 '#pre_render' => [static::class . '::preRenderSlick'],
201 $settings = $build['settings'];
202 if (isset($settings['cache'])) {
203 $suffixes[] = count($build['items']);
204 $suffixes[] = count(array_filter($settings));
205 $suffixes[] = $settings['cache'];
206 $cache['contexts'] = ['languages'];
207 $cache['max-age'] = $settings['cache'];
208 $cache['keys'] = isset($settings['cache_metadata']['keys']) ? $settings['cache_metadata']['keys'] : [$settings['id']];
209 $cache['keys'][] = $settings['display'];
210 $cache['tags'] = Cache::buildTags('slick:' . $settings['id'], $suffixes, '.');
212 if (!empty($settings['cache_tags'])) {
213 $cache['tags'] = array_merge($cache['tags'], $settings['cache_tags']);
216 $slick['#cache'] = $cache;
223 * Builds the Slick instance as a structured array ready for ::renderer().
225 public static function preRenderSlick(array $element) {
226 $build = $element['#build'];
227 unset($element['#build']);
229 $settings = &$build['settings'];
230 if (empty($build['items'])) {
234 // Adds helper class if thumbnail on dots hover provided.
236 if (!empty($settings['thumbnail_style']) && !empty($settings['thumbnail_effect'])) {
237 $dots_class[] = 'slick-dots--thumbnail-' . $settings['thumbnail_effect'];
240 // Adds dots skin modifier class if provided.
241 if (!empty($settings['skin_dots'])) {
242 $dots_class[] = Html::cleanCssIdentifier('slick-dots--' . $settings['skin_dots']);
246 $dots_class[] = $build['optionset']->getSetting('dotsClass') ?: 'slick-dots';
247 $js['dotsClass'] = implode(" ", $dots_class);
250 // Overrides common options to re-use an optionset.
251 if ($settings['display'] == 'main') {
252 if (!empty($settings['override'])) {
253 foreach ($settings['overridables'] as $key => $override) {
254 $js[$key] = empty($override) ? FALSE : TRUE;
258 // Build the Slick grid if provided.
259 if (!empty($settings['grid']) && !empty($settings['visible_items'])) {
260 $build['items'] = self::buildGrid($build['items'], $settings);
264 $build['options'] = isset($js) ? array_merge($build['options'], $js) : $build['options'];
265 foreach (['items', 'options', 'optionset', 'settings'] as $key) {
266 $element["#$key"] = $build[$key];
273 * Returns items as a grid display.
275 public static function buildGrid(array $items = [], array &$settings = []) {
278 // Enforces unslick with less items.
279 if (empty($settings['unslick']) && !empty($settings['count'])) {
280 $settings['unslick'] = $settings['count'] < $settings['visible_items'];
283 // Display all items if unslick is enforced for plain grid to lightbox.
284 // Or when the total is less than visible_items.
285 if (!empty($settings['unslick'])) {
286 $settings['display'] = 'main';
287 $settings['current_item'] = 'grid';
288 $settings['count'] = 2;
291 '#theme' => 'slick_grid',
294 '#settings' => $settings,
296 $slide['settings'] = $settings;
300 // Otherwise do chunks to have a grid carousel, and also update count.
301 $preserve_keys = !empty($settings['preserve_keys']);
302 $grid_items = array_chunk($items, $settings['visible_items'], $preserve_keys);
303 $settings['count'] = count($grid_items);
305 foreach ($grid_items as $delta => $grid_item) {
308 '#theme' => 'slick_grid',
309 '#items' => $grid_item,
311 '#settings' => $settings,
313 $slide['settings'] = $settings;
324 public function build(array $build = []) {
325 foreach (['items', 'options', 'optionset', 'settings'] as $key) {
326 $build[$key] = isset($build[$key]) ? $build[$key] : [];
329 return empty($build['items']) ? [] : [
330 '#theme' => 'slick_wrapper',
333 '#pre_render' => [[$this, 'preRenderSlickWrapper']],
340 public function preRenderSlickWrapper($element) {
341 $build = $element['#build'];
342 unset($element['#build']);
344 if (empty($build['items'])) {
348 // One slick_theme() to serve multiple displays: main, overlay, thumbnail.
349 $defaults = Slick::htmlSettings();
350 $settings = $build['settings'] ? array_merge($defaults, $build['settings']) : $defaults;
351 $id = isset($settings['id']) ? $settings['id'] : '';
352 $id = Slick::getHtmlId('slick', $id);
353 $thumb_id = $id . '-thumbnail';
354 $options = $build['options'];
355 $switch = isset($settings['media_switch']) ? $settings['media_switch'] : '';
357 // Supports programmatic options defined within skin definitions to allow
358 // addition of options with other libraries integrated with Slick without
359 // modifying Optionset like Zoom, Reflection, Slicebox, etc.
360 if (!empty($settings['skin'])) {
361 $skins = $this->getSkinsByGroup('main');
362 if (isset($skins[$settings['skin']]['options'])) {
363 $options = array_merge($options, $skins[$settings['skin']]['options']);
367 // Additional settings.
368 $build['optionset'] = $build['optionset'] ?: Slick::load($settings['optionset']);
370 // Ensures deleted optionset while being used doesn't screw up.
371 if (empty($build['optionset'])) {
372 $build['optionset'] = Slick::load('default');
375 $settings['count'] = empty($settings['count']) ? count($build['items']) : $settings['count'];
376 $settings['id'] = $id;
377 $settings['nav'] = isset($settings['nav']) ? $settings['nav'] : (!empty($settings['optionset_thumbnail']) && isset($build['items'][1]));
378 $settings['navpos'] = !empty($settings['nav']) && !empty($settings['thumbnail_position']);
379 $settings['vertical'] = $build['optionset']->getSetting('vertical');
380 $mousewheel = $build['optionset']->getSetting('mouseWheel');
382 if ($settings['nav']) {
383 $options['asNavFor'] = "#{$thumb_id}-slider";
384 $optionset_thumbnail = Slick::load($settings['optionset_thumbnail']);
385 $mousewheel = $optionset_thumbnail->getSetting('mouseWheel');
386 $settings['vertical_tn'] = $optionset_thumbnail->getSetting('vertical');
390 if ($switch && $switch != 'content') {
391 $settings[$switch] = $switch;
394 $settings['mousewheel'] = $mousewheel;
395 $settings['down_arrow'] = $build['optionset']->getSetting('downArrow');
396 $settings['lazy'] = empty($settings['lazy']) ? $build['optionset']->getSetting('lazyLoad') : $settings['lazy'];
397 $settings['blazy'] = empty($settings['blazy']) ? $settings['lazy'] == 'blazy' : $settings['blazy'];
398 $attachments = $this->attach($settings);
399 $build['options'] = $options;
400 $build['settings'] = $settings;
402 // Build the Slick wrapper elements.
403 $element['#settings'] = $settings;
404 $element['#attached'] = empty($build['attached']) ? $attachments : NestedArray::mergeDeep($build['attached'], $attachments);
406 // Build the main Slick.
407 $slick[0] = self::slick($build);
409 // Build the thumbnail Slick.
410 if (isset($build['thumb'])) {
411 foreach (['items', 'options', 'settings'] as $key) {
412 $build[$key] = isset($build['thumb'][$key]) ? $build['thumb'][$key] : [];
415 $settings = array_merge($settings, $build['settings']);
416 $settings['optionset'] = $settings['optionset_thumbnail'];
417 $settings['skin'] = isset($settings['skin_thumbnail']) ? $settings['skin_thumbnail'] : '';
418 $settings['display'] = 'thumbnail';
419 $build['optionset'] = $optionset_thumbnail;
420 $build['settings'] = $settings;
421 $build['options']['asNavFor'] = "#{$id}-slider";
423 unset($build['thumb']);
424 $slick[1] = self::slick($build);
427 // Reverse slicks if thumbnail position is provided to get CSS float work.
428 if ($settings['navpos']) {
429 $slick = array_reverse($slick);
432 // Collect the slick instances.
433 $element['#items'] = $slick;