1

I'm building an app with Bootstrap and AngularJS. At some point I have an ng-repeat on a col-md-3, listing products. My problem is that I want to be able to insert a collapse into the grid, but as the columns are automatically generated, I don't really know how to do it.

Here's a diagram to understand it better: First, the grid of .col-md-3 is populated from the ng-repeat.

enter image description here

And what I'm trying to achieve, is to add a .col-md-12 that appears right under the row of the .col-md-3 that gets clicked on.

enter image description here

My initial thought was to add an empty .col-md-12 dynamically after each group of 4 .col-md-3, but I wouldn't know how to do so, and it kinda seems to be that it would be a rather dull approach. Any ideas?

Here's the relevant html:

<div class="infinite" infinite-scroll="loadDetails()">
      <div class="col-xs-3 col-md-3" ng-repeat="release in main.releases | filter:main.album">
        <release release="release" artist="main.artist" class="dropdown"></release> 
      </div>
</div>

EDIT: Here's a working Plunker including tasseKATTs solution.

Eric Mitjans
  • 2,149
  • 5
  • 40
  • 69
  • could just add one empty .col-md-12 at the end, and manipulate it through javascript and css – xiaoboa Mar 23 '14 at 14:56
  • @enchante - but how do I put it "in place" (meaning right under the row of elements that contained the element that was clicked on)? – Eric Mitjans Mar 23 '14 at 14:58

2 Answers2

3

Place a custom directive on your inner element together with a position counter that starts with 1 and a marker describing if it's the last element:

<div ng-repeat="item in items" class="col-xs-3">
  <div class="item" the-directive position="{{ $index + 1 }}" last="{{ $last }}">
  </div>
</div>

Create the directive with an isolated scope, bind scope properties to the values of the position and last attributes and attach a click event handler to the element:

app.directive('theDirective', function() {
  return {
    restrict: 'A',
    scope: { position: '@', last: '@' },
    link: function(scope, element, attrs) {
      element.bind('click', function() {
        ...
      });
    }
  };
});

In the click handler first create the collapse element or select it if it already exists:

var collapseQuery = document.querySelector('#collapse');
var collapse = collapseQuery === null ?
angular.element('<div id="collapse" class="col-xs-12"><div class="twelve"></div></div>') :
angular.element(collapseQuery);

Based on the position of the clicked element calculate the rounded number up to the nearest multiple of four:

var calculatedPosition = Math.ceil(scope.position / 4) * 4;

Get the element at the calculated position or the last one if the position is out of range:

var calculatedQuery = document.querySelector('[position="' + calculatedPosition + '"]');
if (calculatedQuery === null) calculatedQuery = document.querySelector('[last="true"]');;

var calculatedElement = angular.element(calculatedQuery);

Insert the collapse element after the element at the calculated position:

calculatedElement.parent().after(collapse);

Could use some optimizations, but hopefully puts you on the right track.

Demo with some extra visuals: http://plnkr.co/edit/fsC51vS7Ily3X3CVmxSZ?p=preview

tasseKATT
  • 38,470
  • 8
  • 84
  • 65
  • 1
    Updated to use a marker attribute describing if the element is the last one or not instead of using document.querySelectorAll. – tasseKATT Mar 24 '14 at 06:31
  • tasseKATT i've tried to add your directive in my code but it breaks. I think it has an issue with the scope, not finding which are the items to be counted, but I cannot see the problem... care to take a look? [This is the file](http://cccctanger.com/discogz/js/directives.js). You'll see I've also added the the-directive position="{{ $index + 1 }}" last="{{ $last }} to the wrapping element inside item (release, in my example) – Eric Mitjans Mar 24 '14 at 13:58
  • Try with replace: false on the release directive. Otherwise both the directives will ask for an isolated scope on the same element, and you cannot have that. – tasseKATT Mar 24 '14 at 14:05
  • Fixed, doesn't break anymore, but according to the console it still doesn't count properly or define the last element [link](http://cccctanger.com/discogz/). In your plunkr, where exactly you tell the directive which are the elements to be counted? – Eric Mitjans Mar 24 '14 at 14:09
  • got it, had the the-directive position="{{ $index + 1 }}" last="{{ $last }} in the wrong element. Thanks!!! – Eric Mitjans Mar 24 '14 at 14:29
  • tasseKAT, I run into another problem, would it be possible to transfer a function from the first directive (getVersions on line 34 of directives) into your directive and trigger it at the same time that the collapse div appears? [Link](http://plnkr.co/edit/GBdvtN6ayo59017rLKPs?p=preview) – Eric Mitjans Mar 24 '14 at 17:31
  • 1
    It's possible. But my solution to your question does not fit well with your real use case. I will give it a look when I have time. – tasseKATT Mar 25 '14 at 18:57
1

This question is easier to answer in an angular way if you follow the bootstrap convention using 12 columns per a row:

Grid columns are created by specifying the number of twelve available columns you wish to span. For example, three equal columns would use three .col-xs-4.

In your case, this means each row can have up to 4 .col-xs-3 columns, or just 1 .col-xs-12. You can prep your data to be displayed this way by splitting it into an array of smaller arrays.

$scope.getRows = function(array) {
var rows = [];

//https://stackoverflow.com/questions/8495687/split-array-into-chunks
var i,j,temparray,chunk = 4;
for (i=0,j=array.length; i<j; i+=chunk) {
    temparray = array.slice(i,i+chunk);

    rows.push(temparray);
}

return rows;
};

$scope.rows = $scope.getRows($scope.main.releases);

Then you can nest ngRepeat to achieve the desired layout, using ng-if to only create a col-xs-12 when a corresponding .col-xs-3 is clicked.

<div ng-repeat="row in rows">
  <div class="row">
    <div class="col-xs-3" ng-repeat="release in row" ng-click="main.releaseClicked=release">
      <div class="release">{{release}}</div> 
    </div>
  </div>
  <div class="row" ng-repeat="release in row" ng-if="main.releaseClicked==release">
    <div class="col-xs-12">
      <div class="detail">Release detail: {{release}}</div>
    </div>
  </div>
</div>

This leaves you with a more declarative view that describes how the app works, and doesn't require jQuery to do DOM manipulation.

Here is a working demo: http://plnkr.co/ujlpq5iaX413fThbocSj

Community
  • 1
  • 1
j.wittwer
  • 9,497
  • 3
  • 30
  • 32
  • I'll try to integrate it into my directive as it seems more simple than the other solution and it won't create scope problems, I'll let you know if I succeed! – Eric Mitjans Mar 24 '14 at 20:26
  • j.wittwer, could you indicate me where to place your js considering my plunkr example? Inside the service or inside script.js? – Eric Mitjans Mar 24 '14 at 20:40
  • it looks like you define your controller in script.js - so I would put the chunking code there. – j.wittwer Mar 24 '14 at 21:18
  • I've implemented your code but I ran into an unexpected problem, once the results are filtered, the effect is kinda weird as it will maintain the row system and not behave like the items are just one after the other (eg. after the filter, 1 element in first row, 3 in second, 2 in third). It was a brilliant idea anyway! – Eric Mitjans Mar 25 '14 at 23:40
  • Do you have a plnkr demonstrating this? It sounds like you are chunking before filtering, when it should be the other way around. – j.wittwer Mar 25 '14 at 23:42
  • [Link](http://plnkr.co/edit/GBdvtN6ayo59017rLKPs?p=preview) Try searching for Burial and filter by "Four" – Eric Mitjans Mar 25 '14 at 23:46
  • 1
    I tried to tweak the placement of the filtering in your plunk, but wasn't able to grok how it all works together. i have a hunch that your code base could be dramatically simplified with a good rewrite. good luck with the project - it is looking pretty cool! – j.wittwer Mar 26 '14 at 02:22