Flame_Phoenix Flame_Phoenix - 16 days ago 5
AngularJS Question

Set Custom Heigh Angular Material Autocomplete Dropdown

Background



I am doing a custom autocomplete with Angular Material and I would like to set the height of the autocomplete dropdown results box to something bigger.

Research



To achieve this, I made a research and concluded that Angular Material does not support this, and that this issue will only be addressed in Angular Material 2 (to be released for AngularJS2):



After reading, I realized that some people found a way around this limitation forcing the CSS (Angular Material Design Layout custom sizes), but not matter what I try I can't see to make any of their suggestions work.

Code



My sample app is composed of an
index.html
, an
autocomplete.js
file, and the
server.js
file with a mockData.json file.

Following are the
index.html
,
autocomplete.js
and
style.css
files. Since I am hosting this example in Cloud9 if the server is ON you can see it run live !



/*global angular*/

"use strict";
angular.module('MyApp', ['ngMaterial', 'ngMessages', 'material.svgAssetsCache', 'ngMdIcons']).controller('DemoCtrl', DemoCtrl);

function DemoCtrl($q, $log, $http) {

this.searchText = null;

this.querySearch = function(query) {
let serverUrl = '//custom-material-autocomplete-fl4m3ph03n1x.c9users.io/getClients';
let deferred = $q.defer();
$http({
method: 'GET',
url: serverUrl,
params: {
word: query
}

}).then(function successCallback(response) {
deferred.resolve(response.data);
}, function errorCallback(response) {
$log.error(response);
});

return deferred.promise;
};

this.searchTextChange = function(text) {
$log.info('Text changed to ' + text);
}

this.selectedItemChange = function(item) {
$log.info('Item changed to ' + JSON.stringify(item));
}
}

.autocompletedemoCustomTemplate .autocomplete-custom-template li {
border-bottom: 1px solid #ccc;
height: auto;
padding-top: 8px;
padding-bottom: 8px;
white-space: normal;
}

.autocompletedemoCustomTemplate .autocomplete-custom-template li:last-child {
border-bottom-width: 0;
}

.autocompletedemoCustomTemplate .autocomplete-custom-template .item-title,
.autocompletedemoCustomTemplate .autocomplete-custom-template .item-metadata {
display: block;
line-height: 2;
}

.autocompletedemoCustomTemplate .autocomplete-custom-template .item-title md-icon {
height: 18px;
width: 18px;
}

<html lang="en">

<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body ng-app="MyApp" ng-cloak>

<div ng-controller="DemoCtrl as ctrl" layout="column" ng-cloak="" class="autocompletedemoCustomTemplate" ng-app="MyApp">
<md-content layout-padding="" layout="column">
<form ng-submit="$event.preventDefault()">

<md-autocomplete md-selected-item="ctrl.selectedItem" md-search-text-change="ctrl.searchTextChange(ctrl.searchText)" md-search-text="ctrl.searchText" md-selected-item-change="ctrl.selectedItemChange(item)" md-items="item in ctrl.querySearch(ctrl.searchText)"
md-item-text="item.name" md-min-length="0" placeholder="Pick an Angular repository" md-menu-class="autocomplete-custom-template">
<md-item-template>

<span class="item-title">
<strong>Company name:</strong> {{item.Company_Name}}
</span>
<strong>Client Ids:</strong>
<span ng-repeat="clientId in item.Assets">
<span class="item-metadata">

<span class="item-metastat">
&emsp;<ng-md-icon icon="subdirectory_arrow_right" style="fill: gray" size="24"></ng-md-icon>
{{clientId}}
</span>
</span>
</span>

</md-item-template>
</md-autocomplete>
</form>
</md-content>
</div>



<!--CSS files-->
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0-rc4/angular-material.min.css">
<link rel="stylesheet" href="https://material.angularjs.org/1.1.0-rc4/docs.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic">
<link rel="stylesheet" href="/css/style.css">

<!-- Angular Material requires Angular.js Libraries -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-animate.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-route.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-aria.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-messages.min.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/t-114/svg-assets-cache.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0-rc4/angular-material.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-material-icons/0.7.0/angular-material-icons.min.js"></script>

<!-- Your application bootstrap -->
<script type="text/javascript" src="js/autocomplete.js"></script>
</body>

</html>





Here are the
server.js
file and the mockData.json file.

"use strict";

//Lets define a port we want to listen to
const PORT = 8080;

//Init Vars
var express = require('express');
var fs = require('fs');

var app = express();

//Init Functions
//we allow CORS: http://enable-cors.org/server_expressjs.html
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});

app.listen(PORT, function() {
console.log('Example app listening on port ' + PORT + '!');
});

//GET methods
app.get('/getClients', function(req, res, next) {

let assetsQuery = function(array, word) {
let result = false;
array.forEach(function(element, index, array) {
if (element.toLowerCase().trim().includes(word))
result = true;
});
return result;
};

//get parameters from GET: https://scotch.io/tutorials/use-expressjs-to-get-url-and-post-parameters
var query = req.param('word');

let dbQuery = JSON.parse(fs.readFileSync('mockData.json', 'utf8'));

let result = [];
if (query == null || query == "" || typeof query == 'undefined')
result = dbQuery.mockupData;
else {
query = query.toLowerCase().trim();
dbQuery.mockupData.forEach(function(element, index, array) {
if (element.Company_Name.toLowerCase().trim().includes(query) || assetsQuery(element.Assets, query))
result.push(element);
});
}

res.send(result);
});


Fake Data file:

{
"mockupData": [{
"AccountID": "cweu4xy733z06mqv96",
"Company_Name": "Wal - Mart Stores, Inc",
"Assets": ["gme-wal1", "gme-wal2"]
}, {
"AccountID": "3tvjnjzud1jz2xy6ug",
"Company_Name": "Volkswagen Automotive",
"Assets": ["gme-vol1", "gme-aut2"]
}, {
"AccountID": "ht019dupmtb4jinzpo",
"Company_Name": "Vitol Commodities",
"Assets": ["gme-vit1", "gme-com2"]
}, {
"AccountID": "l2fd73rbpc48d1hvua",
"Company_Name": "Verizon Telecommunications",
"Assets": ["gme-ver1", "gme-tel2"]
}, {
"AccountID": "2iygtyj2do2pi8e4dv",
"Company_Name": "Valero",
"Assets": ["gme-vale1", "gme-valoil2"]
}, {
"AccountID": "1ojav89f9qka85vpwb",
"Company_Name": "United Health Health care",
"Assets": ["gme-usahc1", "gme-uhhc2"]
}, {
"AccountID": "y9ikfaj2qgf18d0vsw",
"Company_Name": "Trafigura Commodities",
"Assets": ["gme-traf1", "gme-trafcom2"]
}, {
"AccountID": "nxhpt5unxsjedui5sk",
"Company_Name": "Toyota Automotive",
"Assets": ["gme-toy1", "gme-oyota2"]
}, {
"AccountID": "hqu18f8wy43oc5kfde",
"Company_Name": "Total",
"Assets": ["gme-total1", "gme-tot2"]
}, {
"AccountID": "tsc9aures3yjpy2nrr",
"Company_Name": "Tata Group Conglomerate",
"Assets": ["gme-tata1", "gme-grcon2"]
}, {
"AccountID": "paoxb086omzi1uu5zr",
"Company_Name": "State Grid Electric utility",
"Assets": ["gme-sgeu1", "gme-elecs2"]
}, {
"AccountID": "7u2fhcatofgqjzv2tf",
"Company_Name": "Sinopec Group",
"Assets": ["gme-sin1", "gme-sinoil2"]
}, {
"AccountID": "hbg285h3nk206zmdqb",
"Company_Name": "Saudi Aramco",
"Assets": ["gme-sau1", "gme-aram2"]
}, {
"AccountID": "xyg1n25grvl74f69l6",
"Company_Name": "Samsung Conglomerate",
"Assets": ["gme-sam1", "gme-sacomg2"]
}, {
"AccountID": "aaoexfcqln8peec4dv",
"Company_Name": "Royal Dutch Shell",
"Assets": ["gme-royal1", "gme-forthequeen"]
}, {
"AccountID": "rubvrmy2ucrvh3elrj",
"Company_Name": "Phillips 66",
"Assets": ["gme-phil1", "gme-ips2"]
}, {
"AccountID": "5gmxscwazuokverzbd",
"Company_Name": "Petrobras",
"Assets": ["gme-pedro1", "gme-petro2"]
}, {
"AccountID": "nsx4y558obp62dwn47",
"Company_Name": "PDVSA",
"Assets": ["gme-pdvsa1", "gme-pgas"]
}, {
"AccountID": "80o7d5p4wrx1ueygmx",
"Company_Name": "Microsoft Conglomerate",
"Assets": ["gme-microsoft1", "gme-evil2"]
}, {
"AccountID": "1v14j9i5w8sy4iipuv",
"Company_Name": "McKesson Pharmaceuticals",
"Assets": ["gme-mckesson1", "gme-mckeuticals2"]
}, {
"AccountID": "92ac8fl1dk1nh5v408",
"Company_Name": "Lukoil",
"Assets": ["gme-luko1", "gme-loil2"]
}, {
"AccountID": "pn3w8dxqrzytlmanhe",
"Company_Name": "Kuwait Petroleum Corporation",
"Assets": ["gme-kuwait1", "gme-war2"]
}, {
"AccountID": "35mtlyc6bnbxhuav1d",
"Company_Name": "Koch Industries Conglomerate",
"Assets": ["gme-koch1", "gme-kindus2"]
}, {
"AccountID": "n4gu863njqnndvmk3d",
"Company_Name": "Japan Post Conglomerate",
"Assets": ["gme-jap1", "gme-konichiwa2"]
}, {
"AccountID": "gkrllbxd56r9gi4q7t",
"Company_Name": "Industrial and Commercial Bank of China Financial services",
"Assets": ["gme-china1", "gme-LongLiveTsungLee2"]
}, {
"AccountID": "qgb1gudy460seqgzo3",
"Company_Name": "Honda Automotive",
"Assets": ["gme-honhon1", "gme-autohon2"]
}]
}


Problem



How can I make my dropdown box for the results bigger?

Answer

Background

After much experimentation and reading, I finally found a workaround on how to do it.

My solution is not really a solution to the problem, but more of a hack. The truth is that there is no official support for this, and there will never be, at least in Angular Material 1, which has now been deprecated in order to make Angular Material 2, and has lost official support.

Workaround

Thus my workaround focuses on one suggestion found in github, and it effectively overrides the default CSS:

.md-virtual-repeat-container.md-autocomplete-suggestions-container {
    height: 24vh;
    min-height: 12vh;
    max-height: 24vh !important;
}

Explanation & Analysis

This code overrides the default CSS by using !important and setting custom heights. In this case I am using the vh measurement, but you can use the px or any other.

This workaround will work fine if you only have one autocomplete with a dropdownbox, or if all the autocomplete with dropdownboxes you have are equal.

If however, you have two autocompletes different from each other, you will start fighting with your own code because of the override (which will affect all autocompletes).

Conclusion

This is, in the end, not a perfect solution, but as long as you don't go too much into autocomplete, it should be fine.

I hope this can help someone in the future!

Comments