El NativeScript Playground es un lugar en la nube donde puedas dar tus primeros pasos con NativeScript-Vue desde tu navegador. Simplemente ingresa al link y comienza arrastrar y soltar componentes.
Puedes trabajar con el Playground tanto como quieras: usarlo para probar como desarrollar con NativeScript o incluso dearrollar todo tu proyecto en la plataforma. Sin embarjo, cuando llegue el momento de llevar tu aplicación al mundo, vas a necesitar instalar las herramientas de NativeScript localmente y luego elegir uno de los templates disponibles.
Esta sección, consiste en dos partes:
Abriendo este link vas a poder ver un editor de código simple y en la nube, donde un template básico de NativeScript + Vue.js esta precargado para que empieces a usar.
Si esta es tu primera vez aquí, el Playground te solicitará instalar algunas aplicaciones móviles: NativeScript Playground y NativeScript Preview. Ambas te permiten ver en tiempo real, los cambios que realizas en el código sin necesidad de recompilar el mismo.
Puedes obviar este paso, pero si lo haces te vas a perder mucha de la diversión de jugar un poco con Vue.js y NativeScript.
Manten las aplicaciones corriendo mientras experimentas un poco con el código.
La barra lateral izquierda, ofrece un explorador de archivos y un panel Components. La mayor parte de tu tiempo, vas a estar trabajando en el archivo app.js
y app.css
, los cuales contienen la funcionalidad de la aplicación y los estilos de la misma. Por el momento no necesitamos indagar en estos archivos.
El panel de Components provee una acceso rápido a código pre-configurado de los componentes de UI de NativeScript.
Desde la parte superior de la página, puedes subir tus cambios de código para previsualizar las aplicaciones en tu dipositivo, guardar los cambios e incluso descargar el código.
En la parte inferior, encontrarás a tu mejor amigo a la hora de obtener información en tiempo real. En este panel vas a poder leer errores e informacion de logging
Simplemente haz click en un componente del panel y sueltalo en el editor de código (en cualquier lugar dentro del bloque template
). Al soltar el botón del mouse, se inserta un código predeterminado. relacionado al componente en cuestión. Cualquier posible método que relacionado al componente (como el que ocurre al presionar un item), se agrega automáticamente en la parte superior de la página antes del bloque template
.
TIP: Usa la funcionalidad de búsqueda del panel de Components para acceder rápidamente al elemento que necesitas. Ten en cuenta que la búsqueda funciona solo con el título del componente y no con el nombre de código. Por ejemplo: puedes buscar con las palabras text field pero no con TextField.
Para que la mayoría de los componentes funcionen, necesitas soltarlos dentro del bloque <Page>
, preferentemente dentro de un contenedor (layout). Recuerda que los contenedores le permiten a la aplicación, saber como posicionar el contenido dentro de la pantalla.
NOTA: Actualmente, no hay nada que impida que sueltes código en algun lugar que cause que la aplicación colapse o deje de funcionar. En esos casos, debes estar atento a las pestañas Errors y Device Logs para poder detectar el problema.
Una vez que ubicas tu código en un lugar válido, puedres presionar el botón Preview (o Ctrl+S
/Cmd+S
) y ver como tu aplicación se actualiza en tu pantalla de forma instantánea.
En algunos casos, cuando interactuas con tu aplicación, esta puede cerrarse de manera inesperada. Simplemente vuelve a lanzarla y checa los reportes de errores.
Si en algun punto dejas de visualizar en el dispositivo los cambios aplicados, haz clic en el código QR y vuelve a escanearlo.
Ahora el componente se ejecuta y se muestra en la pantalla. Seguramente estas entusiasmado con los reultados pero quieres hacer algo por ti mismo.
Puedes hackear el código sugerido por defecto, cambia tamaños, etiquetas o incluso agrega y quita elementos. Anímate a explorar.
Puedes ir al archivo app.css
y modificar algunos estilos. Por ejemplo, experimenta cambiando colores y tamaños de texto.
Si quieres explorar más a fondo el NativeScript Playground, puedes empezar por crear una simple aplicación to-do con los siguientes requerimientos:
Diseño básico
Funcionalidad básica
Diseño avanzado
TIP: Todas las secciones de este tutorial, contienen subsecciones con Conceptos básicos de NativeScript y Requerimientos de Implementación. Puedes omitir las subsecciones básicas y pasar directamente a las de implementación.
Todo el desarrollo de este tutorial transcurre en los archivos app.js
y app.css
, los cuales contienen la funcionalidad de la aplicación y los estilos de la misma.
El archivo app.js
consiste en una simple declaración de un template
sin singun tipo de funcionalidad extra. Mientras que vayas arrastrando y soltando componentes de UI dentro de la aplicación, el mismo Playground va a ir agregando bloques methods
con el código correspondiente.
Dentro de app.js
, vas a estar trabajando en el bloque template
para diseñar la interfaz de usuario o en el bloque methods
para crear la funcionalidad. El bloque template
requiere XML compatible con NativeScript. El bloque methods
acepta tanto código Vue.js como JavaScript.
Asi es como tu aplicación luce al principio de la sección y como lo hará una vez que finalices la misma.
Pantalla Inicial | Pestaña 1 | Pestaña 2 |
---|---|---|
<Page>
es un elemento de interfaz de primer nivel en una aplicación NativeScript-Vue. Todos los demas elementos estarán anidados dentro de este.
El elemento <ActionBar>
muestra una barra de acción para el elemento<Page>
. Un elemento <Page>
no puede contener más de un elemento <ActionBar>
.
Comunmente, luego del elemento <ActionBar>
, vas a colocar componentes de nevegación (como un drawer o un tab view) o componentes de posicionamiento (layout). Ambos tipos de elemento controlan como distribuir y posicionar el contenido de tu aplicación.
Usa el componente <TabView>
para crear una aplicación con dos pestañas
<ScrollView>
con todo su contenido creado por defecto<ScrollView>
también son elementos de primer nivel utilizados para contenido desplazable.<TabView>
donde lo quieras ubicar.<TabView>
para rellenar toda la pantalla (100%
).<TabViewItem>
y su contenido, para que reflejen el propósito de los mismos.<Label>
sin estilos y sin formatos. Aplica la propiedad textWrap="true"
a los componentes <Label>
para mejorar la visualización del texto.Al final de este paso, tu código deberia asemejarse al siguiente ejemplo:
const Vue = require('nativescript-vue');
new Vue({
template: `
<Page class="page">
<ActionBar title="My Tasks" class="action-bar" />
<TabView height="100%">
<TabViewItem title="To Do">
<Label text="This tab will list active tasks and will let users add new tasks." textWrap="true" />
</TabViewItem>
<TabViewItem title="Completed">
<Label text="This tab will list completed tasks for tracking." textWrap="true" />
</TabViewItem>
</TabView>
</Page>
`,
}).$start();
Asi es como tu aplicación luce al principio de la sección y como lo hará una vez que finalices la misma.
Pantalla Inicial | Pestaña 1 - Sin tareas | Pestaña 1 - Con tareas |
---|---|---|
Los componentes de posicionamiento (layouts), te permiten acomodar los componentes de UI en tu aplicación. Cada vez que necesites colocar más de un componente en patanlla, vas a necesitar usar componentes de posicionamiento. Los componentes <StackLayout>
y <GridLayout>
son las opciones más básicas y versátiles. Permiten ubicar contenido en formal vertical o de grilla, respectivamente. Mientras que <StackLayout>
muestra elementos en una secuencia natural, <GridLayout>
permite elegir de forma más precisa como posicionar los elementos dentro de un sistema de grillas.
Usa el componente <GridLayout>
para acomodar el elemento <TextField>
y el elemento <Button>
dentro de la página. Estos últimos son los componentes que permitirán el ingreso de datos a la aplicación.
Usa el componente <ListView>
para mostrar tareas debajo del campo de texto
<Label>
dentro del primer elemento <TabViewItem>
.<StackLayout>
dentro del primer elemento <TabViewItem>
<Label>
dentro del elemento <StackLayout>
.<GridLayout>
dentro del elemento <StackLayout>
.<Label>
del elemento <GridLayout>
.Configura el componente <StackLayout>
.
Configura el componente <GridLayout>
.
100%
para que ocupe todo el ancho de pantalla.<TextField>
y el componente <Button>
dentro del elemento <GridLayout>
data()
como un bloque methods
(arriba de template
). En los próximos pasos, vas a tener que agregar código a estos bloques para crear la funcionalidad de la aplicación.<ListView>
debajo de la grilla.Configura el componente posicionando los elementos dentro de la grilla.
<TextField>
dentro de la celda perteneciente a la primera fila y primer columna.<Button>
dentro de la celda perteneciente a la primera fila y segunda columna.<TextField>
y al elemento <ListView>
. Establece un alto para el elemento <ListView>
.unshift
(de Array
) para colocar la tarea al principio de la página.Al final de este paso, tu código deberia asemejarse al siguiente ejemplo:
const Vue = require('nativescript-vue');
new Vue({
data() {
return {
todos: [],
textFieldValue: '',
}
},
methods: {
onItemTap(args) {
console.log('Task with index: ' + args.index + ' tapped'); // Imprime en consola la tarea presionada.
},
onButtonTap() {
console.log('New task added: ' + this.textFieldValue + '.'); // Imprime en consola la nueva tarea.
this.todos.unshift({ name: this.textFieldValue }); // Agrega tareas a la colección de ToDo
this.textFieldValue = ''; // Limpia el campo de texto
},
},
template: `
<Page class="page">
<ActionBar title="My Tasks" class="action-bar" />
<TabView height="100%">
<TabViewItem title="To Do">
<!-- Positions an input field, a button, and the list of tasks in a grid. -->
<StackLayout orientation="vertical" width="100%" height="100%">
<GridLayout columns="2*,*" rows="auto" width="100%">
<TextField row="0" col="0" v-model="textFieldValue" hint="Type new task..." editable="true" @returnPress="onButtonTap" /> <!-- Configures the text field and ensures that pressing Return on the keyboard produces the same result as tapping the button. -->
<Button row="0" col="1" text="Add task" @tap="onButtonTap" />
</GridLayout>
<ListView for="todo in todos" @itemTap="onItemTap" height="100%"> <!-- Asegurate de configurar un alto (height) o tu lista no quedará visible en iOS. -->
<v-template>
<Label :text="todo.name" />
</v-template>
</ListView>
</StackLayout>
</TabViewItem>
<TabViewItem title="Completed">
<Label text="This tab will list completed tasks for tracking." textWrap="true" />
</TabViewItem>
</TabView>
</Page>
`,
}).$start();
Asi es como tu aplicación luce al principio de la sección y como lo hará una vez que finalices la misma.
Pestaña 1 - Tareas agregadas | Pestaña 1 - Item Presionado | Pestaña 2 - Tareas completadas |
---|---|---|
Por defecto, el componente <ListView>
detecta el gesto tap
en cada item y emite el evento correspondiente. El evento contiene información con el índice del objeto presionado y la colección de todos los items. Para permitir que el usuario elija que hacer luego del gesto tap
, podriamos lanzar un diálogo cada vez que ocurra el evento en cuestión
El módulo dialogs
esta disponible de forma global y provee diferentes tipos de dialogos: alert
, action
, prompt
, login
y confirmation
. Este ejemplo, utliza el de tipo action()
para permitir que el usuario elija si quiere marcar una tarea como completada o si quiere eliminarla de la lista.
En el segundo elemento <TabViewItem>
, arrastra y suelta el componente <ListView>
. Limpia el contenido por defecto y establece el alto necesario.
En el nuevo componente <ListView>
muestra elementos desde la colección de tareas completadas (dones
).
<ListView for="done in dones" @tap="onDoneTap" height="100%"> <!-- Asegurate de configurar un alto (height) o tu lista no quedará visible en iOS. -->
<v-template>
<Label :text="done.name" />
</v-template>
</ListView>
Modificar el métodoonItemTap
.
El método muestra el diálogo action()
.
El método imprime en consola la selección del usuario.
Basado en ese selección, el método mueve elementos desde la colección de todos
a la colección de dones
, borra elementos de la colección todos
o cierra el diálogo. Usa el método splice()
para evitar "agujeros" en tu colección y unshift()
para asegurarte que las nuevas tareas se muestren al comienzo (ambos son métodos nativos de los objetos Array
de JavaScript).
onItemTap(args) {
action('What do you want to do with this task?', 'Cancel', ['Mark completed', 'Delete forever'])
.then(result => {
console.log(result); // Imprime en consola la opción seleccionada
switch (result) {
case 'Mark completed':
this.dones.unshift(args.item); // Ubica la tarea activa presionada al comienzo de las tareas completadas
this.todos.splice(args.index, 1); // Eliminar la tarea activa presionada
break;
case 'Delete forever':
this.todos.splice(args.index, 1); // Eliminar la tarea activa presionada
break;
case 'Cancel' || undefined: // Cerrar el diálogo
break;
}
})
},
Al final de este paso, tu código deberia asemejarse al siguiente ejemplo:
const Vue = require('nativescript-vue');
new Vue({
data() {
return {
todos: [],
dones: [],
textFieldValue: '',
}
},
methods: {
onItemTap(args) {
action('What do you want to do with this task?', 'Cancel', ['Mark completed', 'Delete forever'])
.then(result => {
console.log(result); // Imprime en consola la opción seleccionada
switch (result) {
case 'Mark completed':
this.dones.unshift(args.item); // Ubica la tarea activa presionada al comienzo de las tareas completadas
this.todos.splice(args.index, 1); // Eliminar la tarea activa presionada
break;
case 'Delete forever':
this.todos.splice(args.index, 1); // Eliminar la tarea activa presionada
break;
case 'Cancel' || undefined: // Cerrar el diálogo
break;
}
})
},
onButtonTap() {
console.log('New task added: ' + this.textFieldValue + '.'); // Imprime en consola la nueva tarea
this.todos.unshift({ name: this.textFieldValue }); // Agrega tareas a la colección de ToDo
this.textFieldValue = ''; // Limpia el campo de texto
},
},
template: `
<Page class="page">
<ActionBar title="My Tasks" class="action-bar" />
<TabView height="100%">
<TabViewItem title="To Do">
<!-- Positions an input field, a button, and the list of tasks in a grid. -->
<StackLayout orientation="vertical" width="100%" height="100%">
<GridLayout columns="2*,*" rows="auto" width="100%">
<TextField row="0" col="0" v-model="textFieldValue" hint="Type new task..." editable="true" @returnPress="onButtonTap" /> <!-- Configures the text field and ensures that pressing Return on the keyboard produces the same result as tapping the button. -->
<Button row="0" col="1" text="Add task" @tap="onButtonTap" />
</GridLayout>
<ListView for="todo in todos" @itemTap="onItemTap" height="100%"> <!-- Asegurate de configurar un alto (height) o tu lista no quedará visible en iOS. -->
<v-template>
<Label :text="todo.name" />
</v-template>
</ListView>
</StackLayout>
</TabViewItem>
<TabViewItem title="Completed">
<ListView for="done in dones" @tap="onDoneTap" height="100%"> <!-- Asegurate de configurar un alto (height) o tu lista no quedará visible en iOS. -->
<v-template>
<Label :text="done.name" />
</v-template>
</ListView>
</TabViewItem>
</TabView>
</Page>
`,
}).$start();
Asi es como tu aplicación luce al principio de la sección y como lo hará una vez que finalices la misma.
Pestaña 2 - Tareas completadas | Pestaña 2 - Item presionado | Pestaña 1 - Tareas activas |
---|---|---|
Esta implementación no requiere ningun conocimiento extra.
Para la segunda pestaña modifica el método onDoneTap
:
El método muestra un diálogo action()
.
El método imprime en consola la selección del usuario (debugging).
Basandose en la selección, el método mueve los elementos desde la colección de dones
a la de todos
, elimina elementos de la colección de dones
o cierra el diálogo. Usa el método splice()
para evitar "agujeros" en tu colección y unshift()
para asegurarte que las nuevas tareas son mostradas al comienzo (ambos son métodos nativos de los objetos Array
de JavaScript).
onDoneTap(args) {
action('What do you want to do with this task?', 'Cancel', ['Mark to do', 'Delete forever'])
.then(result => {
console.log(result); // Imprime en consola la opción seleccionada
switch (result) {
case 'Mark to do':
this.todos.unshift(args.item); // Ubica la tarea completada al comienzo de las tareas
this.dones.splice(args.index, 1); // Remueve la tarea presionada
break;
case 'Delete forever':
this.dones.splice(args.index, 1);
break;
case 'Cancel'||undefined:
break;
}
})
},
Al final de este paso, tu código deberia asemejarse al siguiente ejemplo:
const Vue = require('nativescript-vue');
new Vue({
data() {
return {
todos: [],
dones: [],
textFieldValue: '',
}
},
methods: {
onItemTap(args) {
action('What do you want to do with this task?', 'Cancel', ['Mark completed', 'Delete forever'])
.then(result => {
console.log(result); // Imprime en consola la opción seleccionada
switch (result) {
case 'Mark completed':
this.dones.unshift(args.item); // Ubica la tarea completada al comienzo de las tareascompleted tasks.
this.todos.splice(args.index, 1); // Remueve la tarea presionada
break;
case 'Delete forever':
this.todos.splice(args.index, 1);
break;
case 'Cancel' || undefined:
break;
}
})
},
onDoneTap(args) {
action('What do you want to do with this task?', 'Cancel', ['Mark to do', 'Delete forever'])
.then(result => {
console.log(result); // Imprime en consola la opción seleccionada
switch (result) {
case 'Mark to do':
this.todos.unshift(args.item); // Ubica la tarea completada al comienzo de las tareas
this.dones.splice(args.index, 1); // Remueve la tarea presionada
break;
case 'Delete forever':
this.dones.splice(args.index, 1); // Remueve la tarea presionada
break;
case 'Cancel'||undefined:
break;
}
})
},
onButtonTap() {
console.log('New task added: ' + this.textFieldValue + '.'); // Imprime en consola la nueva tarea
this.todos.unshift({ name: this.textFieldValue }); // Agrega la tarea en la colección ToDo
this.textFieldValue = ''; // Limpia el campo de texto
}
},
template: `
<Page class="page">
<ActionBar title="My Tasks" class="action-bar" />
<TabView height="100%">
<TabViewItem title="To Do">
<!-- Positions an input field, a button, and the list of tasks in a grid. -->
<StackLayout orientation="vertical" width="100%" height="100%">
<GridLayout columns="2*,*" rows="auto" width="100%">
<TextField row="0" col="0" v-model="textFieldValue" hint="Type new task..." editable="true" @returnPress="onButtonTap" /> <!-- Configures the text field and ensures that pressing Return on the keyboard produces the same result as tapping the button. -->
<Button row="0" col="1" text="Add task" @tap="onButtonTap" />
</GridLayout>
<ListView for="todo in todos" @itemTap="onItemTap" height="100%" > <!-- Asegurate de configurar un alto (height) o tu lista no quedará visible en iOS. -->
<v-template>
<Label :text="todo.name" />
</v-template>
</ListView>
</StackLayout>
</TabViewItem>
<TabViewItem title="Completed">
<ListView for="done in dones" @itemTap="onDoneTap" height="100%" > <!-- Asegurate de configurar un alto (height) o tu lista no quedará visible en iOS. -->
<v-template>
<Label :text="done.name" />
</v-template>
</ListView>
</TabViewItem>
</TabView>
</Page>
`,
}).$start()
Asi es como tu aplicación luce al principio de la sección y como lo hará una vez que finalices la misma.
Pestaña 1 - Sin estukis | Pestaña 1 - Boton con estilos | Pestaña 1 - Campo de texto con estilos |
---|---|---|
Cuando trabajas con NativeScript-Vue, puedes usar CSS global o en línea para estilar la aplicación. El CSS global es aplicado primero y se maneja dentro del archivo app.css
(ubicado en la raíz del proyecto). Tambien puedes leer la seccion de estilos de NativeScript.
Con el tipo de selector CSS, puedes seleccionar un componente de la UI y aplicar estilos personalizados. Para seleccionar el componente debes usar su nombre tal cual esta provisto en el código. Por ejemplo para agregar una regla de css a una pestaña debes usar como nombre TabView
.
En el archivo app.css
, cambia font-size
, color
y margin
del componente <TextField>
.
TextField {
font-size: 20;
color: #53ba82;
margin-top: 10;
margin-bottom: 10;
margin-right: 5;
margin-left: 20;
}
En el archivo app.js
, en la línea 63, agrega un id
para el botón.
<Button id="add-task-button" row="0" col="1" text="Add task" @tap="onButtonTap" />
En el archvio app.css
, agrega los estilos para el botón creando un botón colorido y con los bordes redondeados.
#add-task-button {
font-size: 20;
font-weight: bold;
color: white;
background-color: #53ba82;
margin-top: 20;
margin-bottom: 10;
margin-right: 20;
margin-left: 5;
border-radius: 20px;
}
Asi es como tu aplicación luce al principio de la sección y como lo hará una vez que finalices la misma.
Pestañas - Sin estilos | Pestañas - Con estilos |
---|---|
El componente <TabView>
provee algunas propiedades de estilo por defecto. Puedes aplicar textTransform
a los titulos de las pestañas y cambiar la fuentes y colores (tabTextFontSize
, tabTextColor
, selectedTabTextColor
, tabBackgroundColor
).
NOTA: Actualmente,
tabTextFontSize
no funciona en iOS. Por lo tanto no puedes modificar el tamaño de fuente en los titulos de las pestañas.
En el archivo app.js
, en la línea 57, agrega las propiedades selectedTabTextColor
y tabTextFontSize
. Si estás probando en iOS, recuerda que el tamaño de la fuenta no se modificará.
<TabView height="100%" selectedTabTextColor="#53ba82" tabTextFontSize="20" >
textTransform
)En el archivo app.js
, en las líneas 58 y 73, agregar la propiedad textTransform
. Solo puedes usar esta propiedad al nível del elemento <TabViewItem>
.
<TabViewItem title="To Do" textTransform="uppercase" >
<TabViewItem title="Completed" textTransform="uppercase">
Asi es como tu aplicación luce al principio de la sección y como lo hará una vez que finalices la misma.
Tareas activas - Sin estilos | Tareas activas - Sin separador | Tareas activas - Con estilos |
---|---|---|
Los componentes <ListView>
y <Label>
tienen por defecto algunas propiedades que puedes usar para controlar elementos como el separador de la lista o el texto envuelto. Para la mayoría de los cambios de aspecto de los componentes, debes usar estilos CSS (app.css
).
Para agregar reglas de estilos que funcionen para el texto de tareas activas, puedes configurar un id
en el elemento <Label>
.
En el archivo app.js
, en la línea 67, confugura id
para el elemento <Label>
y agrega la propiedad textWrap="true"
. Habilitar textWrap
logrará que el texto se muestre correctamente en la lista.
<Label id="active-task" :text="todo.name" textWrap="true" >
En la línea 65, agrega la propiedad separatorColor
y asignale el valor transparent
. De esta forma, el separador no se visualizará en la lista.
<ListView for="todo in todos" @itemTap="onItemTap" height="100%" separatorColor="transparent">
En el archivo app.css
, crea los estilos para las tareas activas. Puedes configurar font-size
, color
, text-decoration
y también padding
para posicionar mejor el texto. Juega con las propiedades margin
y padding
hasta que encuentres el resultado que deseas.
#active-task {
font-size: 20;
font-weight: bold;
color: #53ba82;
margin-left: 20;
padding-top: 5;
padding-bottom: 10;
}
Asi es como tu aplicación luce al principio de la sección y como lo hará una vez que finalices la misma.
Tareas completadas - Sin estilos | Tareas completadas - Con estilos |
---|---|
Esta sección aplica los conceptos básicos de NativeScript de la sección Diseño avanzado: Tareas activas con estilos personalizados.
En el archivoapp.js
, en la línea 76, confugura id
para el elemento <Label>
y agrega la propiedad textWrap="true"
. Habilitar textWrap
logrará que el texto se muestre correctamente en la lista.
<Label id="completed-task" :text="done.name" textWrap="true" />
En la línea 74, crea un id
, agrega la propiedad separatorColor
y asignale el valor transparent
. De esta forma, el separador no se visualizará en la lista. Puedes usar el id
para crear reglas de estilos y cambiar los márgenes de <ListView>
.
<ListView id="completed-list" for="done in dones" @itemTap="onDoneTap" height="100%" separatorColor="transparent" >
En el archivo app.css
, crea estilos para las tareas completadas. Puedes configurar font-size
, color
, text-decoration
y también padding
para posicionar mejor el texto. Juega con las propiedades margin
y padding
hasta que encuentres el resultado que deseas.
#completed-task {
font-size: 20;
color: #d3d3d3;
margin-left: 20;
padding-top: 5;
padding-bottom: 10;
text-decoration: line-through;
}
Ceare un estilo para el componente <ListView>
y establece un margin-top
. De esta forma, el texto no se mostrará inmediatamente despues del action bar. Puedes jugar con el margen hasta que encuentres el resultado que deseas.
#completed-list {
margin-top: 20;
}