Tutorial con menú lateral (ion-side-menus)

Alexander A. Ramírez M.

En las aplicaciones móviles hay un patrón de navegación muy común que es mediate menú laterales que se activan con algún botón en la barra superior.

ionic proporciona la directiva ion-side-menus que viene acompañada por otras directivas como:

  • ion-side-menus - Contenedor principal del menú y su contenido. Padre de ion-side-menu-content y ion-side-menu.
  • ion-side-menu-content - Es el contenedor principal donde se especifican los elementos de contenido a los cuales se podría navegar a través del menú.
  • ion-side-menu - Es el menú en sí mismo. Se comporta como un ion-view ya que tiene la misma estructura interna. Es decir, puede contener un ion-header-bar, ion-content y un ion-footer-bar entre otros. Es decir es una vista que se utiliza para mostrar el menú.
  • expose-aside-when - Se utiliza para cambiar el comportamiento del menú en formatos (dispositivos) con pantalla más grande como los tablets.
  • menu-toggle - Activa el menú lateral. Se utiliza en botones que activan el menú usualmente en la barra de navegación.
  • menu-close - Cierra el menú lateral. Se utiliza en los elementos del menú para cerrarlos una vez escogida una opción.

El ejemplo sólo tiene la complejidad de entender la estructura de la aplicación utilizando estas directivas. Podríamos verlo de forma visual.

.
+-- ion-nav-bar
|   |
|   +-- ion-nav-back-button
|   |
|   +-- ion-nav-buttons
|   |
|   +-- ion-nav-title
|
+-- ion-nav-view
    |
    +-- ion-tabs
    |   |
    |   +-- ion-tab
    |       |
    |       +-- ion-nav-view (named)
    |           |
    |           +-- ion-view
    |
    +-- ion-view
    |   |
    |   +-- ion-nav-buttons
    |   |
    |   +-- ion-nav-title
    |   |
    |   +-- ion-header-bar
    |   |
    |   +-- div class="sub-header"
    |   |
    |   +-- ion-footer-bar
    |   |
    |   +-- div class="sub-footer"
    |   |
    |   +-- ion-content
    |
    +-- ion-pane
    |
    +-- ion-side-menus
        |
        +-- ion-side-menu-content
        |   |
        |   +-- ion-nav-bar
        |   |   |
        |   |   +-- ion-nav-back-button
        |   |   | 
        |   |   +-- ion-nav-buttons
        |   |
        |   +-- ion-nav-view (named)
        |       |
        |       +-- ion-view
        |           |
        |           +-- ion-nav-buttons
        |           |
        |           +-- ion-nav-title
        |           |
        |           +-- ion-content
        |
        +-- ion-side-menu (side={left|right})
            |
            +-- ion-header-bar
            |
            +-- ion-content
            |
            +-- ion-footer-bar

Ingrese a play.ionic.io para obtener el código para iniciar el tutorial y haga FORK. No olvide hacer FORK.

El objetivo es:

  • Crear una aplicación con múltiples páginas (o vistas) y con navegación a través de un menú lateral.
  • Crear el menú y el resto de las vistas.
  • Definir los estados de la aplicación.
  • Definir los controladores.

Estamos partiendo de una aplicación sin nada.

Primero definamos nuestro contenedor principal con la directiva [ion-nav-view] y luego nuestra primera plantilla menu.html. Dentro de nuestra plantilla hay que definir el contenedor del menú ion-side-menus. Este contenedor cuenta con dos directivas hijas ion-side-menu-content y ion-side-menu. ion-side-menu-content puede tener dos hermanas (siblings) del tipo ion-side-menu que pueden colocarse a la derecha o a la izquierda.

<ion-nav-view>
</ion-nav-view>

<script id="templates/menu.html" type="text/ng-template">
  <ion-side-menus>
    <ion-side-menu-content>
     
    </ion-side-menu-content>
    
    <ion-side-menu side="left">
      
    </ion-side-menu>
  </ion-side-menus>
</script>

Ahora dentro del ion-side-menu-content definamos una barra de navegación con sus botones y la vista de navegación que servirá para desplegar las vistas. Esta vista de navegación ion-nav-view es la que se utiliza en cada plantilla para desplegar contenido.

<script id="templates/menu.html" type="text/ng-template">
  <ion-side-menus>
    <ion-side-menu-content>
      <ion-nav-bar class="bar-assertive">
        <ion-nav-buttons side="left">
 
        </ion-nav-buttons>
      </ion-nav-bar>
      <ion-nav-view name="menuContent">
      </ion-nav-view>
    </ion-side-menu-content>
    
    <ion-side-menu side="left">

    </ion-side-menu>
  </ion-side-menus>
</script>

Completemos la barra de navegación incluyendo el botón para navegar al estado previo ion-nav-back-button y agreguemos el botón que va a activar el menú. Ese botón que activa el menú debe utilizar la directiva menu-toggle que es la que activa el menú del lado que se indique. Esta directiva se coloca en un link o un botón.

<script id="templates/menu.html" type="text/ng-template">
  <ion-side-menus>
    <ion-side-menu-content>
      <ion-nav-bar class="bar-assertive">
        <ion-nav-back-button>
        </ion-nav-back-button>
        <ion-nav-buttons side="left">
          <button class="button button-icon button-clear ion-navicon"
            menu-toggle="left">
          </button>
        </ion-nav-buttons>
      </ion-nav-bar>
      <ion-nav-view name="menuContent">
      </ion-nav-view>
    </ion-side-menu-content>
    
    <ion-side-menu side="left">

    </ion-side-menu>
  </ion-side-menus>
</script>

Ahora vamos a definir el contenido del menú. Este menú es un contenedor que permite definir un ion-header-bar, ion-footer-bar y ion-content. También se podría definir un sub-footer o sub-header. Es una plantilla contenedora que se puede generar dinámicamente utilizando el controlador del menú. Defina una lista con tres elementos que nos permitan navegar a las tres vistas que vamos a crear. Fíjese en el uso de la directiva menu-close. Sin ella el menú no se cierra solo. Esta se debe usar en cada elemento del menú.

<script id="templates/menu.html" type="text/ng-template">
  <ion-side-menus>
    <ion-side-menu-content>
      <ion-nav-bar class="bar-assertive">
        <ion-nav-back-button>
        </ion-nav-back-button>
        <ion-nav-buttons side="left">
          <button class="button button-icon button-clear ion-navicon"
            menu-toggle="left">
          </button>
        </ion-nav-buttons>
      </ion-nav-bar>
      <ion-nav-view name="menuContent">
      </ion-nav-view>
    </ion-side-menu-content>
    
    <ion-side-menu side="left">
      <ion-header-bar class="bar-assertive">
        <h1 class="title">Top</h1>
      </ion-header-bar>
      <ion-content>
        <ion-list>
          <ion-item menu-close href="#/app/presenta">
            Presentación
          </ion-item>
          <ion-item menu-close href="#/app/empleados">
            Empleados
          </ion-item>
          <ion-item menu-close href="#/app/contacto">
            Contacto
          </ion-item>
        </ion-list>
      </ion-content>
      <ion-footer-bar class="bar-stable">
        <h1 class="title">Bottom</h1>
      </ion-footer-bar>
    </ion-side-menu>
  </ion-side-menus>
</script>

Ahora vamos a crear las plantillas de las tres vistas que vamos a utilizar de la manera usual.

<script id="templates/presenta.html" type="text/ng-template">
  <ion-view view-title="Presentación">
    <ion-content>
      <h1>Aquí presentamos</h1>
    </ion-content>
  </ion-view>
</script>

<script id="templates/empleados.html" type="text/ng-template">
  <ion-view view-title="Empleados">
    <ion-content>
      <h1>Empleados</h1>
    </ion-content>
  </ion-view>
</script>

<script id="templates/contacto.html" type="text/ng-template">
  <ion-view view-title="Contacto">
    <ion-content>
      <h1>Contacto</h1>
    </ion-content>
  </ion-view>
</script>

Vamos al modulo principal y definamos los estados y los controladores respectivos. Esto se hace con la rutina .config. Primero se define el estado principal o padre que es donde está el menú. Esto lo que significa es que todos los estados (en esta aplicación) van a ser hijos de este principal. Hay que utilizar el atributo abstract para indicar que no es un estado al que se navega directamente sino que es un padre. Luego definamos

.config(function($stateProvider, $urlRouterProvider) {
  $stateProvider
    .state('app', {
      url: "/app",
      abstract: true,
      templateUrl: "templates/menu.html",
      controller: 'appController'
    });
});

Como puede ver todavía no aparece nada en pantalla. Ahora definamos el resto de los estados y mediante el servicio $urlRouterProvider indiquemos cual es la pantalla por defecto. Revise también la documentación de $stateProvider como referencia.

.config(function($stateProvider, $urlRouterProvider) {
  $stateProvider
    .state('app', {
      url: "/app",
      abstract: true,
      templateUrl: "templates/menu.html",
      controller: 'appController'
    })
    .state('app.presenta', {
      url: "/presenta",
      views: {
        'menuContent': {
          templateUrl: "templates/presenta.html",
          controller: "presentaController"
        }
      }
    })
    .state('app.empleados', {
      url: "/empleados",
      views: {
        'menuContent': {
          templateUrl: "templates/empleados.html",
          controller: "empleadosController"
        }
      }
    })
    .state('app.contacto', {
      url: "/contacto",
      views: {
        'menuContent': {
          templateUrl: "templates/contacto.html",
          controller: 'contactoController'
        }
      }
    });
    
    $urlRouterProvider.otherwise('/app/presenta');
});

Ahora definamos los controladores respectivos.

.controller('appController', function($scope) {
})  

.controller('presentaController', function($scope) {
})

.controller('empleadosController', function($scope) {
})

.controller('contactoController', function($scope) {
})

Ya debe ver una aplicación que se puede navegar a los diferentes estados.

Para darle un poco de más funcionalidad vamos a agregar una lista de empleados en la plantilla de empleados y vamos a crear una nueva plantilla para ver la información específica de un empleado.

Tenemos que crear un estado nuevo y un controlador nuevo para la plantilla de información específica de un empleado. La particularidad de este estado es que en el url podemos indicar que se reciben parámetros que luego vamos a utilizar en el controlador y en la vista.

.state('app.empleado', {
  url: "/empleado/{id:int}",
  views: {
    'menuContent': {
      templateUrl: "templates/empleado.html",
      controller: 'empleadoController'
    }
  }
})

Ahora vamos a definir la vista nueva y modificamos la de Empleados para crear una lista de empleados y cambiar de estado con ui-sref y el parámetro respectivo.

<script id="templates/empleados.html" type="text/ng-template">
  <ion-view view-title="Empleados">
    <ion-content>
      <ion-list>
        <ion-item ui-sref="app.empleado({id:1})">
          Empleado del mes
        </ion-item>
        <ion-item ui-sref="app.empleado({id:2})">
          Otro empleado
        </ion-item>
      </ion-list>
    </ion-content>
  </ion-view>
</script>

Ahora vamos a crear la vista nueva de Empleado y mostremos el parámetro que nos envió la pantalla de Empleados.

<script id="templates/empleado.html" type="text/ng-template">
  <ion-view view-title="Empleado">
    <ion-content>
      <h1>Empleado { { id } }</h1>
    </ion-content>
  </ion-view>
</script>

Este es un flujo muy usual para manejar parámetros a las pantallas siguientes y generar un tipo de comunicación entre las vistas.

Como se puede dar cuenta todavía falta algo. Eso es el controlador de la nueva pantalla que es el que toma el parámetro que envió la pantalla anterior y define en el modelo el que va a consumir la vista. Los parámetros son accesibles a través de $stateParams.

.controller('empleadoController', function($scope, $stateParams) {
  $scope.id = $stateParams.id;
})

Ya tenemos la aplicación completamente funcional.

Si desea, puede ver el resultado. Hicimos otro ejemplo un poco más funcional. Otro ejemplo donde el menú está gestionado por el padre. Otro ejemplo donde juntamos menú lateral y tabs.

Veamos el resultado.