Pārlūkot izejas kodu

Improve Search results with masonry.js (#4334)

Evgeny Padniuk 8 gadi atpakaļ
vecāks
revīzija
466b047923

+ 19 - 0
DependencyInjection/Configuration.php

@@ -73,6 +73,23 @@ class Configuration implements ConfigurationInterface
 
                 ->scalarNode('title')->defaultValue('Sonata Admin')->cannotBeEmpty()->end()
                 ->scalarNode('title_logo')->defaultValue('bundles/sonataadmin/logo_title.png')->cannotBeEmpty()->end()
+
+                ->arrayNode('global_search')
+                    ->addDefaultsIfNotSet()
+                    ->children()
+                        ->scalarNode('empty_boxes')
+                            ->defaultValue('show')
+                            ->info('Perhaps one of the three options: show, fade, hide.')
+                            ->validate()
+                                ->ifTrue(function ($v) {
+                                    return !in_array($v, array('show', 'fade', 'hide'));
+                                })
+                                ->thenInvalid('Configuration value of "global_search.empty_boxes" must be one of show, fade or hide.')
+                            ->end()
+                        ->end()
+                    ->end()
+                ->end()
+
                 ->arrayNode('breadcrumbs')
                     ->addDefaultsIfNotSet()
                     ->children()
@@ -364,6 +381,8 @@ class Configuration implements ConfigurationInterface
                                 'bundles/sonataadmin/vendor/waypoints/lib/shortcuts/sticky.min.js',
                                 'bundles/sonataadmin/vendor/readmore-js/readmore.min.js',
 
+                                'bundles/sonataadmin/vendor/masonry/dist/masonry.pkgd.min.js',
+
                                 'bundles/sonataadmin/Admin.js',
                                 'bundles/sonataadmin/treeview.js',
                             ))

+ 1 - 0
DependencyInjection/SonataAdminExtension.php

@@ -95,6 +95,7 @@ class SonataAdminExtension extends Extension implements PrependExtensionInterfac
             $container->removeDefinition('sonata.admin.lock.extension');
         }
 
+        $container->setParameter('sonata.admin.configuration.global_search.show_empty_boxes', $config['global_search']['empty_boxes']);
         $container->setParameter('sonata.admin.configuration.templates', $config['templates']);
         $container->setParameter('sonata.admin.configuration.admin_services', $config['admin_services']);
         $container->setParameter('sonata.admin.configuration.dashboard_groups', $config['dashboard']['groups']);

+ 2 - 0
Resources/doc/reference/configuration.rst

@@ -218,3 +218,5 @@ Full Configuration Options
                     uses:                 []
             persist_filters:      false
             show_mosaic_button:   true
+            global_search:
+                show_empty_boxes: show

+ 12 - 0
Resources/public/css/styles.css

@@ -501,3 +501,15 @@ div.sonata-filters-box div.form-group span.input-group-addon {
     padding: 3px 10px;
     font-size: 13px;
 }
+
+.sonata-search-result-hide {
+    display: none;
+}
+
+.sonata-search-result-fade {
+    opacity: 0.6;
+}
+
+.sonata-search-result-show {
+    display: block;
+}

+ 50 - 0
Resources/public/vendor/masonry/.bower.json

@@ -0,0 +1,50 @@
+{
+  "name": "masonry",
+  "version": "4.0.0",
+  "description": "Cascading grid layout library",
+  "main": "masonry.js",
+  "dependencies": {
+    "get-size": "~2.0.2",
+    "outlayer": "~2.0.0"
+  },
+  "devDependencies": {
+    "jquery-bridget": "~2.0.0",
+    "qunit": "^1.12",
+    "jquery": ">=1.4.3 <4"
+  },
+  "ignore": [
+    "**/.*",
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests",
+    "sandbox/",
+    "CONTRIBUTING.mdown",
+    "gulpfile.js",
+    "package.json"
+  ],
+  "homepage": "http://masonry.desandro.com",
+  "authors": [
+    "David DeSandro"
+  ],
+  "keywords": [
+    "masonry",
+    "layout",
+    "outlayer"
+  ],
+  "license": "MIT",
+  "moduleType": [
+    "amd",
+    "globals",
+    "node"
+  ],
+  "_release": "4.0.0",
+  "_resolution": {
+    "type": "version",
+    "tag": "v4.0.0",
+    "commit": "707ac6eebf112b2413cee2e9ed6457fba346cabc"
+  },
+  "_source": "https://github.com/desandro/masonry.git",
+  "_target": "~4.0.0",
+  "_originalSource": "masonry"
+}

+ 80 - 0
Resources/public/vendor/masonry/README.mdown

@@ -0,0 +1,80 @@
+# Masonry
+
+_Cascading grid layout library_
+
+Masonry works by placing elements in optimal position based on available vertical space, sort of like a mason fitting stones in a wall. You’ve probably seen it in use all over the Internet.
+
+See [masonry.desandro.com](http://masonry.desandro.com) for complete docs and demos.
+
+## Install
+
+### Download
+
++ [masonry.pkgd.js](https://github.com/desandro/masonry/raw/master/dist/masonry.pkgd.js) un-minified, or
++ [masonry.pkgd.min.js](https://github.com/desandro/masonry/raw/master/dist/masonry.pkgd.min.js) minified
+
+### CDN
+
+Link directly to Masonry files on [npmcdn](https://npmcdn.com/).
+
+``` html
+<script src="https://npmcdn.com/masonry-layout@4.0/dist/masonry.pkgd.js"></script>
+<!-- or -->
+<script src="https://npmcdn.com/masonry-layout@4.0/dist/masonry.pkgd.min.js"></script>
+```
+
+### Package managers
+
+Bower: `bower install masonry --save`
+
+[npm](https://www.npmjs.com/package/masonry-layout): `npm install masonry-layout --save`
+
+## Initialize
+
+With jQuery
+
+``` js
+$('.grid').masonry({
+  // options...
+  itemSelector: '.grid-item',
+  columnWidth: 200
+});
+```
+
+With vanilla JavaScript
+
+``` js
+// vanilla JS
+// init with element
+var grid = document.querySelector('.grid');
+var msnry = new Masonry( grid, {
+  // options...
+  itemSelector: '.grid-item',
+  columnWidth: 200
+});
+
+// init with selector
+var msnry = new Masonry( '.grid', {
+  // options...
+});
+```
+
+With HTML
+
+Add a `data-masonry` attribute to your element. Options can be set in JSON in the value.
+
+``` html
+<div class="grid" data-masonry='{ "itemSelector": ".grid-item", "columnWidth": 200 }'>
+  <div class="grid-item"></div>
+  <div class="grid-item"></div>
+  ...
+</div>
+```
+
+## License
+
+Masonry is released under the [MIT license](http://desandro.mit-license.org). Have at it.
+
+* * *
+
+Made by David DeSandro

+ 41 - 0
Resources/public/vendor/masonry/bower.json

@@ -0,0 +1,41 @@
+{
+  "name": "masonry",
+  "version": "4.0.0",
+  "description": "Cascading grid layout library",
+  "main": "masonry.js",
+  "dependencies": {
+    "get-size": "~2.0.2",
+    "outlayer": "~2.0.0"
+  },
+  "devDependencies": {
+    "jquery-bridget": "~2.0.0",
+    "qunit": "^1.12",
+    "jquery": ">=1.4.3 <4"
+  },
+  "ignore": [
+    "**/.*",
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests",
+    "sandbox/",
+    "CONTRIBUTING.mdown",
+    "gulpfile.js",
+    "package.json"
+  ],
+  "homepage": "http://masonry.desandro.com",
+  "authors": [
+    "David DeSandro"
+  ],
+  "keywords": [
+    "masonry",
+    "layout",
+    "outlayer"
+  ],
+  "license": "MIT",
+  "moduleType": [
+    "amd",
+    "globals",
+    "node"
+  ]
+}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 2416 - 0
Resources/public/vendor/masonry/dist/masonry.pkgd.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 9 - 0
Resources/public/vendor/masonry/dist/masonry.pkgd.min.js


+ 205 - 0
Resources/public/vendor/masonry/masonry.js

@@ -0,0 +1,205 @@
+/*!
+ * Masonry v4.0.0
+ * Cascading grid layout library
+ * http://masonry.desandro.com
+ * MIT License
+ * by David DeSandro
+ */
+
+( function( window, factory ) {
+  // universal module definition
+  /* jshint strict: false */ /*globals define, module, require */
+  if ( typeof define == 'function' && define.amd ) {
+    // AMD
+    define( [
+        'outlayer/outlayer',
+        'get-size/get-size'
+      ],
+      factory );
+  } else if ( typeof module == 'object' && module.exports ) {
+    // CommonJS
+    module.exports = factory(
+      require('outlayer'),
+      require('get-size')
+    );
+  } else {
+    // browser global
+    window.Masonry = factory(
+      window.Outlayer,
+      window.getSize
+    );
+  }
+
+}( window, function factory( Outlayer, getSize ) {
+
+'use strict';
+
+// -------------------------- masonryDefinition -------------------------- //
+
+  // create an Outlayer layout class
+  var Masonry = Outlayer.create('masonry');
+  // isFitWidth -> fitWidth
+  Masonry.compatOptions.fitWidth = 'isFitWidth';
+
+  Masonry.prototype._resetLayout = function() {
+    this.getSize();
+    this._getMeasurement( 'columnWidth', 'outerWidth' );
+    this._getMeasurement( 'gutter', 'outerWidth' );
+    this.measureColumns();
+
+    // reset column Y
+    this.colYs = [];
+    for ( var i=0; i < this.cols; i++ ) {
+      this.colYs.push( 0 );
+    }
+
+    this.maxY = 0;
+  };
+
+  Masonry.prototype.measureColumns = function() {
+    this.getContainerWidth();
+    // if columnWidth is 0, default to outerWidth of first item
+    if ( !this.columnWidth ) {
+      var firstItem = this.items[0];
+      var firstItemElem = firstItem && firstItem.element;
+      // columnWidth fall back to item of first element
+      this.columnWidth = firstItemElem && getSize( firstItemElem ).outerWidth ||
+        // if first elem has no width, default to size of container
+        this.containerWidth;
+    }
+
+    var columnWidth = this.columnWidth += this.gutter;
+
+    // calculate columns
+    var containerWidth = this.containerWidth + this.gutter;
+    var cols = containerWidth / columnWidth;
+    // fix rounding errors, typically with gutters
+    var excess = columnWidth - containerWidth % columnWidth;
+    // if overshoot is less than a pixel, round up, otherwise floor it
+    var mathMethod = excess && excess < 1 ? 'round' : 'floor';
+    cols = Math[ mathMethod ]( cols );
+    this.cols = Math.max( cols, 1 );
+  };
+
+  Masonry.prototype.getContainerWidth = function() {
+    // container is parent if fit width
+    var isFitWidth = this._getOption('fitWidth');
+    var container = isFitWidth ? this.element.parentNode : this.element;
+    // check that this.size and size are there
+    // IE8 triggers resize on body size change, so they might not be
+    var size = getSize( container );
+    this.containerWidth = size && size.innerWidth;
+  };
+
+  Masonry.prototype._getItemLayoutPosition = function( item ) {
+    item.getSize();
+    // how many columns does this brick span
+    var remainder = item.size.outerWidth % this.columnWidth;
+    var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil';
+    // round if off by 1 pixel, otherwise use ceil
+    var colSpan = Math[ mathMethod ]( item.size.outerWidth / this.columnWidth );
+    colSpan = Math.min( colSpan, this.cols );
+
+    var colGroup = this._getColGroup( colSpan );
+    // get the minimum Y value from the columns
+    var minimumY = Math.min.apply( Math, colGroup );
+    var shortColIndex = colGroup.indexOf( minimumY );
+
+    // position the brick
+    var position = {
+      x: this.columnWidth * shortColIndex,
+      y: minimumY
+    };
+
+    // apply setHeight to necessary columns
+    var setHeight = minimumY + item.size.outerHeight;
+    var setSpan = this.cols + 1 - colGroup.length;
+    for ( var i = 0; i < setSpan; i++ ) {
+      this.colYs[ shortColIndex + i ] = setHeight;
+    }
+
+    return position;
+  };
+
+  /**
+   * @param {Number} colSpan - number of columns the element spans
+   * @returns {Array} colGroup
+   */
+  Masonry.prototype._getColGroup = function( colSpan ) {
+    if ( colSpan < 2 ) {
+      // if brick spans only one column, use all the column Ys
+      return this.colYs;
+    }
+
+    var colGroup = [];
+    // how many different places could this brick fit horizontally
+    var groupCount = this.cols + 1 - colSpan;
+    // for each group potential horizontal position
+    for ( var i = 0; i < groupCount; i++ ) {
+      // make an array of colY values for that one group
+      var groupColYs = this.colYs.slice( i, i + colSpan );
+      // and get the max value of the array
+      colGroup[i] = Math.max.apply( Math, groupColYs );
+    }
+    return colGroup;
+  };
+
+  Masonry.prototype._manageStamp = function( stamp ) {
+    var stampSize = getSize( stamp );
+    var offset = this._getElementOffset( stamp );
+    // get the columns that this stamp affects
+    var isOriginLeft = this._getOption('originLeft');
+    var firstX = isOriginLeft ? offset.left : offset.right;
+    var lastX = firstX + stampSize.outerWidth;
+    var firstCol = Math.floor( firstX / this.columnWidth );
+    firstCol = Math.max( 0, firstCol );
+    var lastCol = Math.floor( lastX / this.columnWidth );
+    // lastCol should not go over if multiple of columnWidth #425
+    lastCol -= lastX % this.columnWidth ? 0 : 1;
+    lastCol = Math.min( this.cols - 1, lastCol );
+    // set colYs to bottom of the stamp
+
+    var isOriginTop = this._getOption('originTop');
+    var stampMaxY = ( isOriginTop ? offset.top : offset.bottom ) +
+      stampSize.outerHeight;
+    for ( var i = firstCol; i <= lastCol; i++ ) {
+      this.colYs[i] = Math.max( stampMaxY, this.colYs[i] );
+    }
+  };
+
+  Masonry.prototype._getContainerSize = function() {
+    this.maxY = Math.max.apply( Math, this.colYs );
+    var size = {
+      height: this.maxY
+    };
+
+    if ( this._getOption('fitWidth') ) {
+      size.width = this._getContainerFitWidth();
+    }
+
+    return size;
+  };
+
+  Masonry.prototype._getContainerFitWidth = function() {
+    var unusedCols = 0;
+    // count unused columns
+    var i = this.cols;
+    while ( --i ) {
+      if ( this.colYs[i] !== 0 ) {
+        break;
+      }
+      unusedCols++;
+    }
+    // fit container to columns that have been used
+    return ( this.cols - unusedCols ) * this.columnWidth - this.gutter;
+  };
+
+  Masonry.prototype.needsResizeLayout = function() {
+    var previousWidth = this.containerWidth;
+    this.getContainerWidth();
+    return previousWidth != this.containerWidth;
+  };
+
+  return Masonry;
+
+}));

+ 10 - 4
Resources/views/Block/block_search_result.html.twig

@@ -12,9 +12,15 @@ file that was distributed with this source code.
 {% extends sonata_block.templates.block_base %}
 
 {% block block %}
-    <div class="col-lg-4 col-md-6">
-        <div class="box">
-            <div class="box-header with-border">
+    {% set show_empty_boxes = sonata_admin.adminPool.container.getParameter('sonata.admin.configuration.global_search.empty_boxes') %}
+    {% set visibility_class = '' %}
+    {% if pager and not pager.getResults()|length %}
+        {% set visibility_class = 'sonata-search-result-' ~ show_empty_boxes %}
+    {% endif %}
+
+    <div class="col-lg-4 col-md-6 search-box-item {{ visibility_class }}">
+        <div class="box box-solid box-primary{{ visibility_class }}">
+            <div class="box-header with-border {{ visibility_class }}">
                 {% set icon = settings.icon|default('') %}
                 {{ icon|raw }}
                 <h3 class="box-title">
@@ -23,7 +29,7 @@ file that was distributed with this source code.
 
                 <div class="box-tools pull-right">
                     {% if pager and pager.getNbResults() > 0 %}
-                        <span class="badge">{{ pager.getNbResults() }}</span>
+                        <span class="badge bg-light-blue">{{ pager.getNbResults() }}</span>
                     {% elseif admin.hasRoute('create') and admin.hasAccess('create') %}
                         <a href="{{ admin.generateUrl('create') }}" class="btn btn-box-tool">
                             <i class="fa fa-plus" aria-hidden="true"></i>

+ 1 - 2
Resources/views/Core/search.html.twig

@@ -18,8 +18,7 @@ file that was distributed with this source code.
 
     {% if query is defined and query is not same as(false) %}
         {% set count = 0 %}
-        <div class="row">
-
+        <div class="row" data-masonry='{ "itemSelector": ".search-box-item" }'>
         {% for group in groups %}
             {% set display = (group.roles is empty or is_granted('ROLE_SUPER_ADMIN') ) %}
             {% for role in group.roles if not display %}

+ 1 - 0
bower.json

@@ -5,6 +5,7 @@
         "Thomas Rabaix <thomas.rabaix@gmail.com>"
     ],
     "dependencies": {
+        "masonry": "~4.0.0",
         "jqueryui": "~1.10",
         "x-editable": "^1.5",
         "admin-lte": "^2.3",