How to make a list and grid view toggle switch control that loads in partials in AngularJS?
Asked Answered
B

6

12

I am new to AngularJS and I've been unable to find specific tutorials of a list and grid view toggle switch buttons that loads in two different HTML partials. Read official ng-include, ng-switch official docs and searched SO. Unfortunately, we don't want to use UI-router.

Is loading in two partials (list.html and grid.html) the correct Angular way of coding this?

grid view

list view


The most relevant help I've found are these:

1.http://tutorialzine.com/2013/08/learn-angularjs-5-examples (Example #5)

There was an insightful comment on Example #5:

Nice simple examples - well done. The last example that switches between grid and list views is not very efficient since it creates both options and the shows/hides one. A simpler/better approach would be using a single ul with repeater and ng-switch and then enabling the alternate list elements using ng-switch-case. - Johan

2.http://www.adobe.com/devnet/html5/articles/getting-started-with-angularjs.html

3.https://mcmap.net/q/112725/-create-a-single-html-view-for-multiple-partial-views-in-angularjs/12584774#12584774

4.https://mcmap.net/q/222737/-conditional-ng-include-in-angularjs


My HTML code:

<div class="col-xs-6" ng-controller="ToggleDisplayCtrl">
  <div class="btn-group select-format-container" ng-switch on="selected">
    <button ng-switch-when="true" ng-click="toggleGrid()" type="button"
      class="btn btn-primary" ng-model="formatChoice" ng-disabled="">grid</button>
    <button ng-switch-when="false" ng-click="toggleList()" type="button"
      class="btn btn-primary" ng-model="formatChoice" ng-disabled="">list</button>
  </div>
  <div ng-include src="formatChoice.url" scope="" onload=""></div>
</div>
<!-- col-xs-6  END ToggleDisplayCtrl-->

My JS code:

'use strict';
var app = angular.module('tempApp');

app.controller('ToggleDisplayCtrl', function($scope) {
  $scope.formatChoices = [{
      name: 'grid',
      url: 'partials/grid.html'
    },
    {
      name: 'list',
      url: 'partials/list.html'
    }
  ];

  $scope.selected = true;
  $scope.toggleGrid = function() {
    if (selected) {
      return "partials/grid.html";
    }
    return "main.html";
  };
  $scope.toggleList = function() {
    if (selected) {
      return "partials/list.html";
    }
    return "main.html";
  };
});
Beefy answered 17/3, 2014 at 6:25 Comment(0)
B
7

You should define application controller's scope property that will hold URL of template for ng-include, and bind this property to your directive's scope. Make sure to use isolated scope in your directive in order to avoid side effects while modifying directive's scope. Here is an example of doing it (see comments in the code):

JavaScript

angular.module('app', ['ngRoute'])
  .config(['$routeProvider', function($routeProvider) {
    $routeProvider
      .when('/main', {
        controller: 'appController',
        templateUrl: 'main.html'
      })
      .otherwise({
        redirectTo: '/main'
      });
  }])
  .controller('appController', ['$scope', function($scope) {
    $scope.view = 'list.html'; // <- default template used for ng-include
    $scope.data = [{
      text: '1'
    }, {
      text: '2'
    }, {
      text: '3'
    }, {
      text: '4'
    }, {
      text: '5'
    }, {
      text: '6'
    }];
  }])
  .directive('appView', function() {
    return {
      scope: {
        // link view property of directive's scope to some property
        // in the parent scope (scope of appController) specified in
        // app-view attribute of root element of directive
        view: '=appView'
      },
      replace: true,
      template:
        '<nav class="navbar navbar-default">' +
          '<div class="container">' +
            '<ul class="nav navbar-nav navbar-right">' +
              '<li ng-repeat="v in views" ng-bind="v.name" ' +
                  'ng-class="v.icon" ng-click="switchView(v)"></li>' +
            '</ul>' +
          '</div>' +
        '</nav>',
      link: function(scope, el, attr) {
        scope.views = [{
          name: 'List',
          template: 'list.html',
          icon: 'btn btn-default navbar-btn glyphicon glyphicon-th-list'
        }, {
          name: 'Grid',
          template: 'grid.html',
          icon: 'btn btn-default navbar-btn glyphicon glyphicon-th'
        }];
      },
      controller: ['$scope', function($scope) {
        $scope.switchView = function(view) {
          $scope.view = view.template; // <- modify parent scope view
        }
      }]
    }
  });

Main page of application (index.html)

<html ng-app="app">
  ...
  <body ng-view=""></body>
</html>

Route template (main.html)

<header app-view="view"></header>
<section ng-include="view"></section>

List view template (list.html)

<div class="container">
  <div class="row">
    <div class="col-md-12 col-sm-12 panel panel-default" ng-repeat="item in data">
      <div class="panel-body">{{item.text}}</div>
    </div>
  </div>
</div>

Grid view template (grid.html)

<div class="container">
  <div class="row">
    <div class="col-md-4 col-sm-6 panel panel-default" ng-repeat="item in data">
      <div class="panel-body">{{item.text}}</div>
    </div>
  </div>
</div>

Plunker: http://plnkr.co/edit/uWw7NuPG0I161mHXZg2r?p=preview

Bonus: grid is responsive, just play a bit with a window size

Another option:

As you have probably noticed, grid.html and list.html are very similar, so if you have only these two options you may decide not to use ng-include with separate switchable views at all, but place content view directly into your route's template and just switch classes used in panels using ng-class directive which can switch classes when view is changed.

Route template (main.html)

<header app-view="view"></header>
<section>
  <div class="container">
    <div class="row">
      <div ng-class="{'col-md-4': view === 'grid.html',
                      'col-md-12': view === 'list.html',
                      'panel':true, 'panel-default':true}"
           ng-repeat="item in data">
        <div class="panel-body">{{item.text}}</div>
      </div>
    </div>
  </div>
</section>
Bashuk answered 17/3, 2014 at 7:54 Comment(3)
Thanks for this amazing answer. Took me a while to understand it. Modified it to use <button> instead.Beefy
If ng-repeat wasn't used, I can add a different ng-disabled="" per button. How would I incorporate ng-disabled in a ng-repeat? I'd like to show the button (and view) is clicked even when I click off of button.Beefy
You can use functions defined in controller's $scope in ng-disabled while using ng-repeat, pass some values as arguments and return boolean value from that function. Here is an example: plnkr.co/edit/EAenS3lZGVdSgWPH4aWc?p=preview If you need more detailed answer, please post another question on SO and add a link to it in the comment.Bashuk
C
4

Basically what you need to do is put a list view and grid same time on the same page and display one at a time, toggling between them (changing css class) with the switch button. I'll discuss the Example #5 here:

First layout variable in the scope:

$scope.layout = 'grid';

Here are the switch buttons:

<!-- On click change "$scope.layout = list" and if layout=='list' add class 'active' to self-->
<a href="#" class="list-icon" ng-class="{active: layout == 'list'}" ng-click="layout = 'list'"></a>

<!-- On click change "$scope.layout = grid" and if layout=='grid' add class 'active' to self-->
<a href="#" class="grid-icon" ng-class="{active: layout == 'grid'}" ng-click="layout = 'grid'"></a>

These are the grid and list blocks:

<!-- Layout=='grid' if layout parameter is grid than show this block -->
<ul ng-show="layout == 'grid'" class="grid">...</ul>

<!-- Layout=='list' if layout parameter is list than show this block -->
<ul ng-show="layout == 'list'" class="list">...</ul>

Css that makes the list and grid layout:

/*-------------------------
  List layout
--------------------------*/

ul.list {
  list-style: none;
  width: 500px;
  margin: 0 auto;
  text-align: left;
}

ul.list li {
  border-bottom: 1px solid #ddd;
  padding: 10px;
  overflow: hidden;
}

ul.list li img {
  width: 120px;
  height: 120px;
  float: left;
  border: none;
}

ul.list li p {
  margin-left: 135px;
  font-weight: bold;
  color: #6e7a7f;
}
/*-------------------------
  Grid layout
--------------------------*/

ul.grid {
  list-style: none;
  width: 570px;
  margin: 0 auto;
  text-align: left;
}

ul.grid li {
  padding: 2px;
  float: left;
}

ul.grid li img {
  width: 280px;
  height: 280px;
  display: block;
  border: none;
}
Croak answered 27/1, 2015 at 12:1 Comment(0)
R
4

The easiest way to do this is:

<div class="bar">
  <h1>Contacts</h1>
  <a href="#" class="list-icon" ng-class="{active: layout == 'list'}" ng-click="layout = 'list'"></a>
  <a href="#" class="grid-icon" ng-class="{active: layout == 'grid'}" ng-click="layout = 'grid'"></a>
</div>
<div ng-show="layout == 'list'" class="list">
<!-- Add your list page here -->
<div ng-show="layout == 'grid'" class="grid">
<!-- Add your grid page here -->

CSS:

/*
  Contacts bar with toggle switches
*/

.bar {
  background-color: #5c9bb7;
  background-size: 100% 100%;
  box-shadow: 0 1px 1px #ccc;
  border-radius: 2px;
  height: 100px;
  padding: 10px;
  position: relative;
  text-align: right;
  line-height: 1;
}

.bar a {
  background: #4987a1 center center no-repeat;
  width: 32px;
  height: 32px;
  display: inline-block;
  text-decoration: none !important;
  margin-right: 5px;
  border-radius: 2px;
}

.bar a.active {
  background-color: #c14694;
}

.bar a.list-icon {
  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA GXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkYzNkFCQ0ZBMTBCRTExRTM5NDk4RDFEM0E5RkQ1NEZCIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkYzNkFCQ0ZCMTBCRTExRTM5NDk4RDFEM0E5RkQ1NEZCIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6RjM2QUJDRjgxMEJFMTFFMzk0OThEMUQzQTlGRDU0RkIiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6RjM2QUJDRjkxMEJFMTFFMzk0OThEMUQzQTlGRDU0RkIiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7h1bLqAAAAWUlEQVR42mL8////BwYGBn4GCACxBRlIAIxAA/4jaXoPEkMyjJ+A/g9MDJQBRhYg8RFqMwg8RJIUINYLFDmBUi+ADQAF1n8ofk9yIAy6WPg4GgtDMRYAAgwAdLYwLAoIwPgAAAAASUVORK5CYII=);
}

.bar a.grid-icon {
  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjBEQkMyQzE0MTBCRjExRTNBMDlGRTYyOTlBNDdCN0I4IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjBEQkMyQzE1MTBCRjExRTNBMDlGRTYyOTlBNDdCN0I4Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MERCQzJDMTIxMEJGMTFFM0EwOUZFNjI5OUE0N0I3QjgiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MERCQzJDMTMxMEJGMTFFM0EwOUZFNjI5OUE0N0I3QjgiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4MjPshAAAAXklEQVR42mL4////h/8I8B6IGaCYKHFGEMnAwCDIAAHvgZgRyiZKnImBQsACxB+hNoDAQyQ5osQZIT4gH1DsBZABH6AB8x/JaQzEig++WPiII7Rxio/GwmCIBYAAAwAwVIzMp1R0aQAAAABJRU5ErkJggg==);
}
/*-------------------------
  List layout
--------------------------*/

ul.list {
  list-style: none;
  width: 500px;
  margin: 0 auto;
  text-align: left;
}

ul.list li {
  border-bottom: 1px solid #ddd;
  padding: 10px;
  overflow: hidden;
}

ul.list li img {
  width: 120px;
  height: 120px;
  float: left;
  border: none;
}

ul.list li p {
  margin-left: 135px;
  font-weight: bold;
  color: #6e7a7f;
}
/*-------------------------
  Grid layout
--------------------------*/

ul.grid {
  list-style: none;
  width: 570px;
  margin: 0 auto;
  text-align: left;
}

ul.grid li {
  padding: 2px;
  float: left;
}

ul.grid li img {
  width: 280px;
  height: 280px;
  display: block;
  border: none;
}
Rosaline answered 18/6, 2016 at 21:48 Comment(0)
P
3

Here's a simple working JSBin: http://jsbin.com/jomiquqi/1/edit

The ng-include directive takes a variable for the template to include. So to update your code to get it working, your toggleGrid() method shouldn't be returning something, it should be setting a variable on the scope which you pass to ng-include like in my example.

Panocha answered 17/3, 2014 at 6:47 Comment(4)
I need external HTML content and not inline. Thanks for teaching me about ng-template!Beefy
That's not a problem. I used inline templates just because it's simpler to make an example that way. Just do it like my example but change the template paths to your real paths.Panocha
I thought that ng-template could only use inline HTML. How would you change the template path to external HTML files?Beefy
You don't have to change anthing, just point it to the url of your template, just like in your routes or directives.Panocha
E
1

For better understanding of toggling action to see the list and grid view: follow this link and I hope this is helpful.

In this we used Jquery, if you are experimenting on this. Do place this: <script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>

Ensnare answered 17/2, 2017 at 11:7 Comment(0)
E
0

A simple lightweight solution, which is built using a AngularJS Material library without any directives. When the user clicks listView, then container's layout becomes a column without wrapping, and when user clicks gridView, it becomes a row with line wrapping.

'use strict';
const application = angular.module("application", ['ngMaterial']);
application.controller('applicationCtrl', ['$scope', function(scope) {
  scope.toggleGridList = function(newValue) {
    if (newValue === "gridView") {
      scope.value = 'gridView';
      scope.layout = 'row';
    } else {
      scope.value = 'listView';
      scope.layout = 'column';
    }
  };
  scope.onInit = function() {
    scope.toggleGridList('listView');
  };
}]);
.grid-list-toggle {
  padding: 0 !important;
  margin: 8px;
  border-radius: 2px;
  border: 1px solid #c8e6c9;
}

.grid-list-button {
  margin: 0 !important;
  min-width: 0 !important;
  min-height: 0 !important;
  border-radius: 0 !important;
}

.grid-list-button.selected {
  background-color: #c8e6c9;
}
<head>
  <link rel="stylesheet" href="https://material.angularjs.org/1.1.24/angular-material.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.0/angular.min.js"></script>
  <script src="https://code.angularjs.org/1.8.0/angular-animate.min.js"></script>
  <script src="https://code.angularjs.org/1.8.0/angular-aria.min.js"></script>
  <script src="https://material.angularjs.org/1.1.24/angular-material.min.js"></script>
</head>

<body ng-app="application" ng-controller="applicationCtrl"
      ng-init="onInit();" class="ng-cloak" style="overflow: hidden;">
  <!-- Header -->
  <div layout="row" md-whiteframe="1" layout-align="start center">
    <!-- Title -->
    <div layout-padding>
      <span class="md-display-1" style="color: #1b5e20;">Grid-list toggle button</span>
    </div>
    <!-- Divider -->
    <div flex></div>
    <!-- Grid-list toggle button -->
    <div layout="row" layout-padding class="grid-list-toggle">
      <md-button class="grid-list-button" ng-click="toggleGridList('listView')"
          aria-label="List" ng-class="value == 'listView' ? 'selected' : ''">
        <div style="height: 30px">
          <img src="https://i.sstatic.net/SOqYv.png">
        </div>
      </md-button>
      <md-button class="grid-list-button" ng-click="toggleGridList('gridView')"
          aria-label="Grid" ng-class="value == 'gridView' ? 'selected' : ''">
        <div style="height: 30px">
          <img src="https://i.sstatic.net/STQJn.png">
        </div>
      </md-button>
    </div>
  </div>
  <!-- Page content -->
  <div flex="100" layout="column" md-whiteframe="1">
    <div layout="{{layout}}" layout-padding style="overflow-y: auto;"
        ng-class="value == 'gridView' ? 'layout-wrap' : 'layout-nowrap'">
      <div layout="row" layout-padding layout-align="start center" class="grid-list-toggle"
          ng-repeat="number in [].constructor(10) track by $index">
        <img src="https://i.sstatic.net/WANVK.png">
        <div layout="column">
          <span>Title {{$index}}</span>
          <span>Description</span>
          <span>Another line</span>
        </div>
      </div>
    </div>
  </div>
</body>

See: «Grid-list toggle button».

Eldin answered 21/8, 2020 at 19:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.