15 posts

AngularJS - Simple Collapse Directive

Collapse is a common control used in web pages. Users can click to expand or collapse it. Bootstrap has a simple declarative way to create collapse. But Bootstrap's collapse doesn't work if the markup is generated dynamically using AngularJS, because it relies on element id to match target element. For example, following code doesn't work because id is generated dynamically using {{dynamic}}Collapse.

<a class="btn btn-primary" data-toggle="collapse" href="#{{dynamic}}Collapse" aria-expanded="false" aria-controls="{{dynamic}}Collapse">  
Link with href  
<div class="collapse" id="{{dynamic}}Collapse">


angular-ui collapse directive seems to be a good solution, but it requires new variable in the scope object. So I created a simple directive with jQuery to solve this issue.

angular.module('DemoApp', [])  
.controller('DemoCtrl', ['$scope', function($scope) {
  $scope.colors = ['Red', 'Green', 'Blue'];
.directive('collapseToggler', function(){
  return {
    restrict: 'A'
    link: function(scope, elem, attrs) {
      elem.on('click', function() {

collapseToggler directive is applied to the toggler. When clicked, it finds the siblings with CSS class collapse and toggle CSS class in which controls display of the target element.

The limitation of this solution is that it requires the target element to be as the sibling of the toggle element. But most of the times this is the desired DOM structure.

Below is an example of how to use it.

<body ng-controller="DemoCtrl">  
  <div ng-repeat="color in colors">
    <div collapse-toggler class="toggler">What's the color?</div>
    <div class="collapse">

See live example:

See the Pen Simple Collapse Directive by Fu Cheng (@alexcheng) on CodePen.

AngularJS - Fix "Referencing DOM nodes in Angular expressions is disallowed"

When using AngularJS, sometimes you may see this error "Referencing DOM nodes in Angular expressions is disallowed". This may be caused by returning a jQuery expression in your scope functions.

For example, following code will have this issue.

angular.module('test', [])  
    .controller('thing', ['$scope', function ($scope) {
        $scope.action = function() {
            return $("#hello").text("World");

This is common when using CoffeeScript, because CoffeeScript adds a return statement by default.

For example, when using CoffeeScript, it's common to have code like this:

$scope.action = () ->

The code above will have this issue. A simple fix for this is:

$scope.action = () ->

AngularJS - Simple Input Text Count

With AngularJS's two-way data binding, it's very easy to count the characters while user is typing. Usually this will need to use JavaScript to watch keyup event on input or textarea elements. But with AngularJS, it's very simple. No JavaScript is required.

As code shown below, use ng-model to bind textarea to model message. Once message is changed by user input, {% raw %}{{ message.length }}{% endraw %} will display characters count.

<div>{% raw %}{{ message.length }}{% endraw %} of 120</div>  
<textarea ng-model="message" cols="30" rows="10"></textarea>  

See this JSFiddle for the code.

Introduction to iframe shim

iframe shim is an old technique which emerges from old days when browser plugins were popular. When a brower plugin is added to the browser window, it opens a hole in the browser window. If you try to show any other content in the plugin's area, the content will be hidden behind the plugin and won't show up.

To show content above plugin window, you need to create an iframe shim. iframe element can display above plugin window. So it can be used as a layer between plugin window and actual content. The iframe's size is the same as the actual content, but with a lower z-index value.

A typical usage of iframe shim has following structure:

<div class="iframeshim-container">  
  <div class='content-container'>
    <!-- Acutal content -->
  <iframe class="shim-iframe" frameborder="0" scrolling="no"></iframe>

Certain CSS is required to make sure the iframe is the same as actual content and displays below the actual content. The z-index of iframe is a very low value -10000.

.iframeshim-container {
  z-index: 100;
  position: relative;

.iframeshim-container .content-container { 
  margin: 0;
  padding: 0;
  overflow: hidden;
  width: 100%;
  height: 100%;

.iframeshim-container .shim-iframe {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: -10000;

Please note, the HTML structure of iframe shim is very important. Make sure it uses the same structure as the HTML markup shown above. For example,

<div class="iframeshim-container">  
  <div class='content-container'>
    <p>Sample content</p>
  <iframe class="shim-iframe" frameborder="0" scrolling="no"></iframe>

Use with Google Earth web plugin

In the code below, <div> with id google-earth takes up the whole space of parent element. Without iframe shim, <p> with text Hello world won't appear. After using iframe shim, the text will show above the plugin window.

  <div id="google-earth">
  <div class="iframeshim-container">
    <div class='content-container'>
      <p>Hello world</p>
    <iframe class="shim-iframe" frameborder="0" scrolling="no"></iframe>

jQuery plugin

I created a simple jQuery plugin jquery-iframeshim. This plugin can be used to wrap any HTML element into an iframe shim. For example,

  classNames: 'shim1'

CoffeeScript Notes and Tips

CoffeeScript is a popular language used by many frontend developers, I collected some useful notes and tips.

Find first/last element in array

CoffeeScript's destructuring assignment can be used to easily get array's first and last element.

[first, ..., last] = 'a,b,c'.split(',')
[first, ...] = 'a,b,c'.split(',')
[..., last] = 'a,b,c'.split(',')

Conditional(Tenary) operator

CoffeeScript has no conditional(tenary) operator ?:, use following instead:

valid = if length > 0 then true else false  

and it will be compiled to JavaScript like this:

var valid;

valid = length > 0 ? true : false;  

Loop with condition

Given an array, it's very easy to loop each element and process it, like below:

process(item) for item in items  

If you want to only process certain elements, you can use following code:

process(item) for item in items when isValid(item)  

Bind this using =>

Using CoffeeScript's fat arrow (=>), we don't need to use another
variable to capture this any more.

elem.addEventListener('onclick', () =>  

Return value

In CoffeeScript, function always return its final value, so you don't need to use return explicitly in last statement.

myFunc = () ->  
  val = 'Hello'

is the same as

myFunc = () ->  
  val = 'Hello'
  return val

Iterate object properties

To iterate objects in CoffeeScript, use for ... of, like below:

for key,value of hash  
  process(key, value)

If you want to iterate only object's own properties, use for own ... of, like below:

for own key,value of hash  
  process(key, value)

and it's compiled to following JavaScript code with hasOwnProperty check.

var key, value,  
  __hasProp = {}.hasOwnProperty;

for (key in hash) {  
  if (!__hasProp.call(hash, key)) continue;
  value = hash[key];
  process(key, value);

Use Grunt to build web applications

Web application development is getting much more complicated now. A typical web application may have a lot of JavaScript and CSS files. New techniques like CSS pre-processors and CoffeeScript are also popular. This means that a build process needs to be applied before a web application can be published. Some typical tasks need to be done, including:

  • Compress JavaScript files
  • Generate CSS files
  • Compile CoffeeScript files

Usually we use different tools to run different tasks. Grunt is a Node.js based tool that automates these tasks. Grunt is easy to install, just follow this guide. Typically, you just need to install Node.js and nmp, then type following command to install Grunt CLI.

npm install -g grunt-cli  

After Grunt is installed, you need to create a package.json in the root directory of your web application. In this file, you declare the dependencies of your web application. In the sample application, CoffeeScript, LESS and RequireJS are used, so Grunt plugins to run these tasks are included.

  "name": "myapp",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-contrib-coffee": "~0.7.0",
    "grunt-contrib-less": "~0.5.1",
    "grunt-contrib-requirejs": "~0.4.0"

Then you need to add a Gruntfile.js or Gruntfile.coffee to configure the tasks to build your applications.

module.exports = function(grunt) {

    pkg: grunt.file.readJSON('package.json'),
    coffee: {
      compile: {
        files : {
          'public/js/main.js' : 'views/main.coffee',
    requirejs: {
      compile: {
        options: {
          baseUrl: "public/js",
          out: "public/js/main-min.js",
          mainConfigFile : 'public/js/main.js',
          optimize: 'uglify2',
          name : 'lib/almond',
          include : ['main']
    less : {
      production: {
        options: {
          paths: ["public/css"],
          yuicompress: true
        files: {
          "public/css/style.css": "views/style.less"


  grunt.registerTask('default', ['coffee', 'requirejs', 'less']);


In the above Gruntfile.js, three tasks are configured. The first task is to compile CoffeeScript files. The second task is to use RequireJS to build and compress JavaScript files. The last task is to compile LESS files. You can use grunt coffee, grunt requirejs or grunt less to run these three tasks. Or use grunt to run all three tasks.

Grunt team has developed a lot of plugins for popular tasks. There are also a lot of plugins contributed by the community. All these plugins can be found at here. These plugins are Node.js modules.

Install Grunt on Vagrant VMs

If you are going to install Grunt on Vagrant VMs, make sure you edit Vagrantfile to add following settings:

config.vm.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/v-root", "1"]

More details can be found at here.

地图说微博 - 新浪微博应用
















    InfoQ中文站是一个面向中高端技术人员的在线独立社区,为Java、.NET、 Ruby、SOA、敏捷、架构等领域提供及时而有深度的资讯、高端技术大会如QCon、免费迷你书下载如《架构师》等。