Guia de introdução ao desenvolvimento de templates

Introdução Topo

O Style System 2 (S2) é uma linguagem de programação que permite ao utilizador escrever estilos (templates) para a plataforma de Blogs do SAPO. Em S2 o Blog é representado por classes de objectos, o que permite ao utilizador ter um maior controlo no modo como o conteúdo do Blog é representado.

Modelo de Representação do Blog Topo

O Blog é representado por um conjunto de views:

Recent Page Topo

Página principal do Blog, onde são mostrados os últimos posts.

Exemplo: http://blogs.blogs.sapo.pt

Entry Page Topo

Página que mostra um post e todos os comentários (e discussões).

Exemplo: http://blogs.blogs.sapo.pt/430.html

Reply Page Topo

Página de resposta a um post (ou comentário).

Exemplo: http://blogs.blogs.sapo.pt/430.html?mode=reply

Month Page Topo

Página de arquivo dos posts por mês.

Exemplo: http://blogs.blogs.sapo.pt/2006/03/

Day Page Topo

Página de arquivo dos posts por dia.

Exemplo: http://blogs.blogs.sapo.pt/2006/03/06/

Tags Page Topo

Página com as estatisticas de utilização das tags.

Exemplo: http://blogs.blogs.sapo.pt/tag/

Year Page Topo

Página com o resumo dos meses do ano que tem posts.

Exemplo: http://blogs.blogs.sapo.pt/2006/

Conteúdo das Views Topo

Cada view é composta por vários objectos, que vão representar o conteúdo da página.

Algumas das classes mais importantes:

Entry

Representa um post no blog.

A classe Entry tem vários atributos:

Comment

Representa um comentário a um post ou a outro comentário.

A classe Comment tem os seguintes atributos:

User

Representa o autor do post ou comentário.

A classe User tem os seguintes atributos:

Personalização do Blog Topo

A personalização do Blog é feita quando criamos um estilo S2 para o Blog.

Os estilos S2 podem ser criados com ajuda de um interface gráfico (no caso da personalização básica e intermédia) ou então manualmente (personalização avançada).

Estrutura de um Estilo Topo

Um estilo é um conjunto de 1 até 6 layers (camadas) de tipos diferentes que vão definir o aspecto e funcionalidades do Blog.

Existem os seguintes layers:

Um estilo tem sempre que incluir o layer Core. Os layers I18N, Theme e User só podem ser associados ao estilo se existir um layer Layout.

O Estilo criado por omissão para todos os Blogs é composto por 3 layers: Core, I18NC (PT1) e Layout (SAPO).

Personalização Básica Topo

Para criar um novo estilo para o Blog vamos à página de personalização e escolhemos o Layout pretendido ao clicar no botão “Usar este template”. Esta acção vai criar um novo estilo com os layer Core, I18NC e Layout.

Personalização Intermédia Topo

Na página de personalização intermédia temos um conjunto de propriedades que podemos personalizar no nosso Blog. Ao gravarmos as nossas personalizações é criado um novo layer do tipo User com os novos valores das propriedades. O estilo personalizado fica assim definido com 4 layers: Core, I18NC, Layout e User.

Personalização Avançada Topo

Na página de personalização avançada podemos definir uma nova cascade style sheet (css) para o nosso Blog. O Editor de css cria um layer do tipo Theme, onde define a nova css do Blog. O estilo personalizado fica definido com 5 layers: Core, I18NC, Layout, Theme e User.

Nesta página também podemos fazer a gestão de todos os nossos layers e estilos e consultar a documentação.

A documentação está dividida nos seguintes tópicos:

Gestão de layers

Nesta área podemos criar, editar e apagar os nossos layers.

A página está divida em duas secções:

Na secção “Your layers” podemos editar e apagar as layers.

Na secção “Create Layer” podemos criar novas layers. Esta secção está divida em duas partes:

As layers do tipo Layout são definidas na secção Top Level Layer porque estas layers estão associadas à layer Core.

As layers do tipo I18N, Theme e User são definidas na secção Layout Specific Layers porque tem de ficar associadas a uma layer do tipo Layout.

Editor de layers

O Editor de layers divide-se em 3 partes:

A Barra lateral está dividida em 4 tabs:

Na Área de mensagens aparecem as mensagens de sucesso ou de erro quando gravamos ou compilamos o layer.

Gestão de estilos

Nesta área podemos criar, editar, associar e apagar os nossos estilos.

A página está dividida em duas secções:

Na lista de estilos podemos editar, apagar e associar um estilo ao nosso Blog.

Na página de edição / criação de um estilo vamos escolher os layers que vão fazer parte do estilo.

Desenvolvimento de um Layer do tipo Layout Topo

Para criar um novo layer vamos à área de personalização avançada e escolhemos o link “As suas layers” na secção de Opções Avançadas.

Nesta página vamos poder fazer a gestão das nossas layers. Podemos criar, editar e apagar layers.

Para criar uma nova layer do tipo layout vamos à secção “Create top-level layer”, escolhemos a opção “layout” no campo “Type” e clicamos no botão “Create”. O novo layer aparece na secção “Your layers”.

O próximo passo é editar o layer, para criarmos o novo estilo. Clicamos no botão “Edit” correspondente ao layer que vamos editar e vamos para o editor de layers.

Na Área de edição aparece o conteúdo do nosso layer. Nas primeiras linhas temos que identificar o layer. Para isso usamos o objecto layerinfo:

layerinfo "type" = "layout";
layerinfo "name" = "";

O campo type é obrigatório e indica qual o tipo do layer (layout, i18n, theme ou user).

O campo name é opcional, mas deve ser preenchido para identificarmos o layer nas outras áreas de personalização do Blog.

O próximo passo é a customização de cada uma das views do Blog. Cada view do Blog é representada por uma classe e temos que redifinir os métodos print* de cada uma dessas classes.

Page Topo

A classe Page é uma classe genérica, que não tem representação directa no Blog. Todas as outras classes que representam uma view herdam os métodos e atributos da classe Page. Por isso as redifinições dos métodos desta classe passam para todas as views.

Na classe Page vamos redifinir os métodos print() e print_entry().

O método print() vai definir a organização e o aspecto do Blog:

O método print_entry() vai definir a organização e o aspecto dos posts:

RecentPage Topo

A classe RecentPage representa a view dos últimos posts no Blog.

Como o aspecto do Blog e dos posts já estão definidos na classe Page, só falta definir como é que vamos apresentar a lista com os últimos posts. Por isso vamos redifinir o método print_body().

EntryPage Topo

A classe EntryPage representa a view de um post e comentários associados.

Nesta view temos que redifinir os seguintes métodos:

O método print_body() define como vão ser representados o post e a lista de comentários.

O método print_comments() define como vai ser representada a lista de comentários.

O método print_comment() define como vai ser representado um comentário:

O método print_comment_partial() define como vai ser representado um comentário parcial (colapsado):

ReplyPage Topo

A classe ReplyPage representa o form de resposta a um post ou comentário.

Nesta view vamos redifinir o método print_body(), que define como vai ser mostrado o form de resposta.

MonthPage Topo

A classe MonthPage representa o arquivo mensal de posts.

Nesta view vamos redifinir os seguintes métodos:

O método print_body() define como vão ser representados os posts do mês do arquivo.

O método print_subjectlist() define como vai ser representada a lista de posts do mês de arquivo.

DayPage Topo

A classe DayPage representa os posts de um dia do mês.

Nesta view vamos redifinir o método print_body() para representar a lista de posts do dia do mês.

Wizard de personalização Topo

Em cada layer do tipo Layout é possivel usar ou definir propriedades que podem representar:

As propriedades podem ser personalizadas no wizard de personalização intermédia. A principal vantagem deste wizard é que permite alterar os valores destas propriedades sem alterar o Layout. As alterações são guardadas em layers do tipo User, que ficam associados ao Blog onde foram criados.

Podemos definir propriedades dos seguintes tipos:

Cada um dos tipos de propriedade pode ser representado por um ou vários componentes no wizard de personalização:

No Layout podem ser definidas propriedades que não são visiveis no wizard, por dependerem de valores de outras propriedades. Neste caso, estas propriedades têm de ser inicializadas na função prop_init().

As propriedades são agrupadas por funcionalidade, e os grupos são representados por Tabs no wizard de personalização.

Gravar e Compilar o layer Topo

No topo do editor temos um botão “Save & Compile”, que grava e compila o layer. Quando carregamos no botão, aparece uma mensagem na àrea de mensagens (Build Pane) a indicar o sucesso da operação ou uma mensagem de erro a indicar em que linha ocorreu o erro.

Exemplo Topo

###############################################################################
#
# Exemplo de referencia de um layer do tipo Layout
#


###############################################################################
#
# Identificação do Layer
# Estes dados não são opcionais.
# 
layerinfo type = "layout";
layerinfo name = "demo";



###############################################################################
#
# Nomes dos Grupos de propriedades
#
# Os nomes aqui definidos aparecem nos tabs da personalização intermédia
#
propgroup imagem = "Imagem";
propgroup cores = "Cores";


###############################################################################
#
# Propriedades
#
# Declaração de propriedades, por grupo
#
propgroup imagem {
	property string intro_imagem {}

	property string bg_image {
		des = "Imagem de fundo";
		note = "Indique o url da imagem.";
		url = "1";
	}
	property string bg_image_repeat {
		des = "Repetição da imagem de fundo";
		values = "repeat|Repetir imagem|no-repeat|Não repetir";
	}
	property string bg_image_align {
		des = "Alinhamento da imagem de fundo";
		values = "center|Centrada|top|No topo|bottom|Em baixo";
	}
	property string bg_image_options {
		des = "Opções da Imagem de fundo";
        noui = 1;		
	}
}
propgroup cores {
	property Color color_bg {
		des = "Cor de fundo?";
	}
}	

###############################################################################
#
# Funções do Core
# 
# function prop_init ()
#    Função para inicialização das propriedades.
#    Esta é a primeira função a ser executada quando a página do Blog é mostrada
#    
# function print_stylesheet ()
#    Função que gera a stylesheet do Blog
#    Esta função é chamada no método Page::print
#

function prop_init () {
    #
    # A Fazer: Escrever o código de inicialização das propriedades.
    #
    # Exemplo:
    #    
    if ($*bg_image == "" ) {
        # If url to background image is empty, 
        # do not include image options to stylesheet
        $*bg_image_options = "";
    }
    else {                
        $*bg_image_options = """url('$*bg_image') $*bg_image_repeat $*bg_image_align""";
    }
}

function print_stylesheet () {
    #
    # A Fazer: Escrever o conteúdo da stylesheet
    #
    # Exemplo:
    #    
    println """        
        body { margin:10px; background: $*bg_image_options $*color_bg; }
        p { margin:0px;}
        .clear { clear: both; height: 0px; line-height: 0px; font-size: 1px;}        
""";

}


###############################################################################
#
# Novas funções
#
# function print_entry (Page p, Entry e, Color bgcolor, Color fgcolor, bool hide_text)
#    Função que escreve o conteúdo de um post
#

function print_entry (Page p, Entry e, Color bgcolor, Color fgcolor, bool hide_text) {
    #
    # A Fazer: Escrever código para conteúdo do post
    #
    # Exemplo:
    #
    var string subject = $e.subject ? $e.subject : "...";
    var string time_post = $e.time->time_format();
    var string username = $e.poster.name;	# Nickname

    if (not $hide_text) {
        println """<div class="title">$subject</div>""";
        $e->print_text();
        
        # User name
        var string user_signature = "Publicado por $username às $time_post";
    
        # Post Link
        var string entry_link = """<a href="$e.permalink_url">Link do post</a>""";

        # Build signature
        var string sign = "$user_signature";
        if ($entry_link != "") { $sign = $sign + " | $entry_link"; } 

        # Print signature
        println """<div class="sign">$sign</div>""";
        println """<br class="clear" />""";
        println """<div class="sepB"></div> """;
    }
}


###############################################################################
#
# Page
# 
# Classe genérica que representa uma página do Blog.
# Todas as views do Blog herdam os atributos e métodos desta classe.
#
# function Page::print()
#    Método principal para gerar uma página do Blog.
#
# function Page::print_entry (Entry e)
#    Método que escreve o conteúdo de um post numa página do Blog.
#

function Page::print() {
    #
    # A Fazer: Escrever código para gerar a página do Blog
    #
    # Exemplo:
    #    
    var string title = $.global_title;
    var string subtitle = $.global_subtitle;

    # Print page
    print """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" """;
    println """ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">""";
    println "<html>";
    
    # Print head
    println "<head>";

    # Include stylesheet
    println """<style type="text/css">""";
    print_stylesheet();     
    println """</style>""";

    $this->print_head();    # Include rss/atom feed links
    println """<title>$.global_title</title>""";
    println "</head>";
    
    # Print body
    println """
<body>
<div id="container">
<!--header-->
<div id="header">
<div class="title"><a href="$.base_url">$title</a></div>
<div class="descri">$subtitle</div>
</div><br class="clear" />
<!--header-->

<!--posts-->
<div id="posts">
""";
    $this->print_body();    # Prints page content
    println """
</div>
<!--/posts-->
<br class="clear" />
</div>
</body>
</html>
""";
}

function Page::print_entry (Entry e) {
    #
    # Método para escrever um post numa página do Blog.
    #
    # Exemplo:
    #
    print_entry($this, $e, null Color, null Color, false);
}


###############################################################################
#
# RecentPage
#
# Últimos posts no Blog. 
# É a página de entrada do Blog.
#
# function RecentPage::print_body ()
#    Escreve os últimos posts
#

function RecentPage::print_body () {
    #
    # A Fazer: Escrever código para mostrar os últimos posts
    #
    # Exemplo:
    #
    var string date_post;
    
    foreach var Entry e ($.entries) {
	    $date_post = $e.time->date_format("long_day");
	    println """<div class="date">$date_post</div>""";
        print_entry($this, $e, null Color, null Color, false);
    }
}


###############################################################################
#
# EntryPage
# 
# Representa um post e os seus comentários
#
# function EntryPage::print_body ()
#    Escreve um post e os comentários ao post.
#
# function EntryPage::print_comments (Comment[] cs)
#    Escreve lista de comentários.
#
# function EntryPage::print_comment (Comment c)
#    Escreve um comentário.
#
# function EntryPage::print_comment_partial (Comment c)
#    Escreve um comentário parcial.
#

function EntryPage::print_body () {
    #
    # A Fazer: Escrever código para a página do post
    #
    # Exemplo:
    #

    # Date Post
    var string date_post = $.entry.time->date_format("long_day");
    println """<div class="date">$date_post</div>""";

    # Print Entry
    print_entry($this, $.entry, null Color, null Color, false);

    # Print Comments    
    var int num_comments = size $.comments;
    if ($num_comments > 0) {
    	println """
<div class="sepB"></div>
<div id="comentar">
<div class="title">Comentários:</div>
"""; 
    	$this->print_comments($.comments);

        var string reply_link;
        var string reply_url = $.entry.comments.post_url;
        $reply_link = """<a href="$reply_url">Comentar post</a>""";
    
    	println """
<!--link comentar post-->
<p>$reply_link</p>
<div class="sepB"></div>
<!--/link comentar post-->
</div>
<div class="sepB"></div>
<!--/comentar-->
""";
    }

}


function EntryPage::print_comments (Comment[] cs) {
    #
    # A Fazer: Escrever código para mostrar os comentários ao post
    #
    # Exemplo:
    #
    if (size $cs == 0) { return; }
    foreach var Comment c ($cs) {
        if (not($c.screened or $c.deleted)) {
            if ($c.full) {
                $this->print_comment($c);
            }
            else {
                $this->print_comment_partial($c);
            }
	     println """<div class="sep"></div>""";

            # Print Threads
	        $this->print_comments($c.replies);
	    }
    }
}


function EntryPage::print_comment (Comment c) {
    #
    # A Fazer: Escrever código para mostrar um comentário
    #
    # Exemplo:
    #
    var string poster;
    var string poster_url = "";

    # Poster
    if (defined $c.poster) {
    	var string poster_url = $c.poster.user_url{"userinfo"};
        $poster = """<a href="$poster_url">$c.poster.name</a>"""; 
    }
    else {
        $poster = "Anónimo";
    }

    # Comment date
    var string date_comment = $c.time->date_format("long") + " às " + $c.time->time_format();

    # Navigation link to comment (threads) 
    println """<a name="$c.anchor"></a>""";

    # Comment text
    println """
<div class="campo1">De:</div>
<div class="campo2C"><em>$poster</em></div>
<br class="clear" />

<div class="campo1">Data:</div>
<div class="campo2C">$date_comment</div>
<br class="clear" />

<div class="campo1">Comentário:</div>
<div class="campo2C"><span class="txt">$c.text</span></div>
<br class="clear" />
""";

    # Comment links
    var string link_list = "";
    var string list_sep = "";

    var string reply_link = "";
    var string thread_url = "";
    var string parent_url = "";

    if ($c.frozen) {
	    $reply_link = "Congelado";
    }
    else {
    	var string reply_url = $c.reply_url;
    	var string reply_options = "";
    	$reply_link = """<a href="$reply_url" $reply_options>Responder comentário</a>""";
    }
    $link_list = $reply_link;
    $list_sep = " | ";
    
    if ($c.parent_url != "") { 
	    $link_list = $link_list + $list_sep + """<a href="$c.parent_url">Início discussão</a>"""; 
    }
    if ($c.thread_url != "") {
	    $link_list = $link_list + $list_sep + """<a href="$c.thread_url">Responder ao comentário</a>"""; 
    }

    println """
<div class="campo1"></div>
<div class="campo2C"><span class="options">$link_list</span></div>
<br class="clear" />
<div class="sepB"></div>
""";

}


function EntryPage::print_comment_partial (Comment c) {
    #
    # A Fazer: Escrever código para mostrar um comentário parcial
    #
    # Exemplo:
    #    
    var string poster;
    var string poster_url = "";

    # Poster
    if (defined $c.poster) {
    	var string poster_url = $c.poster.user_url{"userinfo"};
        $poster = """<a href="$poster_url">$c.poster.name</a>"""; 
    }
    else {
        $poster = "Anónimo";
    }

    # Comment date
    var string date_comment = $c.time->date_format("long") + " às " + $c.time->time_format();

    # Navigation link to comment (threads) 
    println """<a name="$c.anchor"></a>""";

    # Comment text
    println """
<div class="campo1">De:</div>
<div class="campo2C"><em>$poster</em></div>
<br class="clear" />

<div class="campo1">Data:</div>
<div class="campo2C">$date_comment</div>
<br class="clear" />
""";

    # Comment links
    var string link_list = """<a href="$c.permalink_url">Ler comentário</a> """;

    println """
<div class="campo1"></div>
<div class="campo2C"><span class="options">$link_list</span></div>
<br class="clear" />
<div class="sepB"></div>
""";

}


###############################################################################
#
# ReplyPage
#
# Representa a página de Resposta a um post (ou comentário)
#
# ReplyPage::print_body()
#     Escreve o form de resposta.
# 

function ReplyPage::print_body {
    #
    # A Fazer: Escrever código para mostrar um form de resposta ao post (ou comentário)
    #
    # Exemplo:
    #
    if (not $.entry.comments.enabled) {
        print "<h2>$*text_reply_nocomments_header</h2><p>$*text_reply_nocomments</p>";
        return;
    }

    # Date Post
    var string date_post = $.entry.time->date_format("long_day");
    println """<div class="date">$date_post</div>""";

    # Print Entry
    print_entry($this, $.entry, null Color, null Color, false);

    # Comments
    println """
<div class="sepB"></div>
<div id="comentar">
<div class="title">Comentar:</div>
""";
    
    $.form->print();    # Reply Form

    println """
<br class="clear" />
</div>
<div class="sepB"></div>
""";
    
}


###############################################################################
#
# MonthPage
# 
# Representa o Arquivo Mensal.
#
# MonthPage::print_body ()
#    Escreve os posts publicados num mês
#
# MonthDay::print_subjectlist ()
#    Escreve os posts publicados num dia
#

function MonthPage::print_body () {
    #
    # A Fazer: Escrever código para mostrar todos os post do mês
    #
    # Exemplo:
    #
    var bool print_div = false;
    
    foreach var MonthDay d (reverse $.days) {
        if ($d.has_entries) {
    	    if ($print_div) {
    		    println """<div class="sep"></div>""";
    	    }
            $d->print_subjectlist();
    	    $print_div = true;
        }
    }
}


function MonthDay::print_subjectlist () {
    #
    # A Fazer: Escrever código para mostar todos os posts de um dia
    #
    # Exemplo:
    #
    var Page p = get_page(); 
    var bool print_date = true;
    var string date_post;

    foreach var Entry e (reverse $.entries) {
    	if ($print_date) {
    	    $date_post = $e.time->date_format("long_day");
    	    println """<div class="date">$date_post</div>""";
    	}
        print_entry($p, $e, null Color, null Color, false);
	    $print_date = false;
    }
}


###############################################################################
#
# DayPage
# 
# Representa o Arquivo Diário
#
# DayPage::print_body()
#    Escreve os posts de publicados num dia
#

function DayPage::print_body() {
    #
    # A Fazer: Escrever código para mostrar todos os posts publicados num dia
    #
    # Exemplo:
    #
    if ($.has_entries) {
        # Date Post
        var string date = $.date->date_format("long_day");
        println """<div class="date">$date</div>""";

        # Print Entries
        foreach var Entry e (reverse $.entries) {
            $this->print_entry($e);
        }

    } else {
        "<p>$*text_noentries_day</p>";
    }
}