[原创中文翻译]symfony askeet24:第十四天,标签II。

October 7, 2007 – 12:50 am

[欢迎转载,转载请注名出处http://symfony.net.cn。本文英文版权归symfony官方网站所有]

Add tags to a question

给问题填加多个标签

The form

表单

Not only do we want to give the ability to a registered user to add a tag for a question, we also want to suggest one of the tags given to other questions before if they match the first letters he/she types. This is called auto complete. If you ever played with google suggest, you know what this is about.

我们不仅想让注册读者给问题标注标签,还想实现在读者输入标签第一个字母自动出现字母相同的标签,从而读者可以直接选择标签,这叫自动完成。google suggest(www.google.com)就有此功能。

Yesterday, we created a fragment that is inserted in the sidebar when a question detail is displayed. Edit this askeet/apps/frontend/modules/sidebar/templates/_question.php file to add a form at the end:

昨天,我们创建了当问题细节显示时就插入侧边拦的代码片断。修改askeet/apps/frontend/modules/sidebar/templates/_question.php文件,在后面加个表单:

...
<?php if ($sf_user->isAuthenticated()): ?>
<div>Add your own:
<?php echo form_remote_tag(array(
'url' => ‘@tag_add’,
‘update’ => ‘question_tags’,
)) ?>
<?php echo input_hidden_tag(’question_id’, $question->getId()) ?>
<?php echo input_auto_complete_tag(’tag’, ”, ‘tag/autocomplete’, ‘autocomplete=off’, ‘use_style=true’) ?>
<?php echo submit_tag(’Tag’) ?>
</form>
</div>
<?php endif; ?>

Of course, as a tag has to be linked to a user, the addition of a new tag is restricted to authenticated users. We will talk in a minute about the form_remote_tag() helper. But first, let’s have a look at the auto complete input tag. It specifies an action (here, tag/autocomplete) to get the array of matching options.

当然了,标签和用户关联,添加标签功能限制注册读者使用。我们花点时间谈谈form_remote_tag() helper。但首先,让我们看看自动完成输入标签。这里详细介绍了一个动作(标签/自动完成)来得到匹配选项的数组。

Autocomplete

自动完成

The list that the action should return is a list of tags entered by the user that match the entry in the tag field, without duplicates, ordered by alphabetical order. The SQL query that returns this is:

动作返回用户输入的、在标签数据表里匹配到的一列标签,而且不重复,按照字母排序。用SQL查询是这样的:

SELECT DISTINCT tag AS tag
FROM question_tag
WHERE user_id = $id AND tag LIKE $entry
ORDER BY tag

Add this action to the modules/tag/actions/action.class.php file:

给模块/tag/actions/action.class.php文件增加动作:

public function executeAutocomplete()
{
$this->tags = QuestionTagPeer::getTagsForUserLike($this->getUser()->getSubscriberId(), $this->getRequestParameter('tag'), 10);
}

As usual, the heart of the database query lies in the model. Add the following method to the QuestionTagPeer class:

像往常一样,数据库的核心查询封装在模型里。给QuestionTagPeer类加几个方法:

public static function getTagsForUserLike($user_id, $tag, $max = 10)
{
$tags = array();

$con = Propel::getConnection();
$query = ‘
SELECT DISTINCT %s AS tag
FROM %s
WHERE %s = ? AND %s LIKE ?
ORDER BY %s
‘;

$query = sprintf($query,
QuestionTagPeer::TAG,
QuestionTagPeer::TABLE_NAME,
QuestionTagPeer::USER_ID,
QuestionTagPeer::TAG,
QuestionTagPeer::TAG
);

$stmt = $con->prepareStatement($query);
$stmt->setInt(1, $user_id);
$stmt->setString(2, $tag.’%');
$stmt->setLimit($max);
$rs = $stmt->executeQuery();
while ($rs->next())
{
$tags[] = $rs->getString(’tag’);
}

return $tags;
}

Now that the action has determined the list of tags, we only need to shape them in the autocompleteSuccess.php template:

现在动作获得了一列标签,我们仅仅需要把他们显示在autocompleteSuccess.php模板里:

<ul>
<?php foreach ($tags as $tag): ?>
<li><?php echo $tag ?></li>
<?php endforeach; ?>
</ul>

Add a new routing.yml route (and use it instead of the module/action in the input_auto_complete_tag() call of the _question.php partial):

增加新的routing.yml路由(用它来替换模块的动作通过调用_question.php局部模板显示input_auto_complete_tag(),):

tag_autocomplete:
url: /tag_autocomplete
param: { module: tag, action: autocomplete }

And configure your view.yml:

给你的view.yml增加配置:

autocompleteSuccess:
has_layout: off
components: []

Go ahead, you can try it: After registering with an existing account (for instance: fabpot/symfony), display a question and notice the new field in the sidebar. Type in the first letters of a tag already given by this user (for instance: relatives) and watch the div which appears below the field, suggesting the appropriate entry.

测试一下:在注册用户身份登录(例如:fabpot/symfony),显示问题,注意侧边栏显示的新内容。输入其他读者使用标签的第一个字母(例如:relatives),观察提示层出现的内容。

http://www.symfony-project.com/images/askeet/1_0/autocomplete.gif

Remote form

Remote表单

When the form is submitted, there is no need to refresh the full page: Only the list of tags and the form to add a tag have to be refreshed. That’s the purpose of the form_remote_tag() helper, which specifies the action to be called when the form is submitted (tag/add), and the zone of the page to be updated by the result of this action (the element identified ‘question_tags’). This has already been explained during the eighth day, with the AJAX form to add a question.

当表单提交后,不需要刷新整个页面:只有标签列表和添加标签的表单需要刷新。这是form_remote_tag() helper的目的,详细介绍了当表单被提交时(tag/add),当页面被动作发送的结果更新(和question_tags一样)时,动作会被调用。第八天已经解释过了,用AJAX表单提交一个问题。

Let’s create the executeAdd() method in the tag actions:

在标签动作里创建executeAdd()方法:

public function executeAdd()
{
$this->question = QuestionPeer::retrieveByPk($this->getRequestParameter('question_id'));
$this->forward404Unless($this->question);

$userId = $this->getUser()->getSubscriberId();
$phrase = $this->getRequestParameter(’tag’);
$this->question->addTagsForUser($phrase, $userId);

$this->tags = $this->question->getTags();
}

And the addTagsForUser in the Question class:

在Question类里加addTagsForUser

public function addTagsForUser($phrase, $userId)
{
// split phrase into individual tags
$tags = Tag::splitPhrase($phrase);

// add tags
foreach ($tags as $tag)
{
$questionTag = new QuestionTag();
$questionTag->setQuestionId($this->getId());
$questionTag->setUserId($userId);
$questionTag->setTag($tag);
$questionTag->save();
}
}

The addSuccess.php template will determine the code that will replace the update zone. As usual with AJAX actions, it contains a simple include_partial():

addSuccess.php模板确定即将被替换掉更新的代码。和一般的AJAX动作一样,包含简单的include_partial()局部模板:

<?php include_partial('tag/question_tags', array('question' => $question, 'tags' => $tags)) ?>

Add a new routing.yml route:

增加新的routing.yml路由规则:

tag_add:
url: /tag_add
param: { module: tag, action: add }

And configure your view.yml:

给view.yml增加配置

addSuccess:
has_layout: off
components: []

Test it

测试一下

Try it on: Login to the site, display a question detail, enter a new tag and submit. The whole list updates, and the new tag inserts were it should in the alphabetical order.

测试一下:登录站点,显示问题细节,输入新标签并提交。整个列表都更新了,新的标签被插入按字母排序。

Display the tag bubble

标签冒泡法排序

Folksonomy allows to rate a tag with a popularity. But the amount of tags make a list of tags difficult to read. The most satisfying solution, visually speaking, is to increase the size of a tag word according to its popularity, so that the most popular tags - the ones that are given most by users - appear immediately. Check the del.icio.us popular tags page to understand what a tag bubble is.

一般人习惯根据人气来评价标签。但是标签太多就难以阅读。最好的解决方案是通过视觉效果来判断,根据人气多少来控制标签文字大小,那么最富人气的标签——用户使用最多的标签——立刻显示出来。看看del.icio.us网站人气标签页面看看标签冒泡排序的效果。

80% of the visits to a website concern less than 20% of its content, that’s a rule that many website verify every day, and askeet will probably be no different. So if askeet proposes a list of tags, it will have to be arranged by popularity as well, to limit the perturbation of the most unpopular tags (’grandma’, ‘chocolate’) and to increase the visibility of the most popular ones (’php’, ‘real life’, ‘useful’).

80%的网站访问者只关注了此网站少于20%的内容,这是许多网站每天调查的结果,askeet也不例外。所以如果askeet计划显示一列标签,必须按照人气来排序,限制大部分不会流行的标签(例如:’grandma’, ‘chocolate’)提高常用标签的访问性(例如:’php’, ‘real life’, ‘useful’)。

Extend the QuestionTagPeer class

扩展QuestionTagPeer类

The provider of the list of popular tags cannot be another class than QuestionTagPeer. Extend it with a new method, in which we will experiment an alternative way of writing SQL queries:

显示一列流行标签只能用QuestionTagPeer的类。加个新方法,我们将用一种特殊的方法来写sql查询:

public static function getPopularTags($max = 5)
{
$tags = array();

$con = Propel::getConnection();
$query = ‘
SELECT ‘.QuestionTagPeer::NORMALIZED_TAG.’ AS tag,
COUNT(’.QuestionTagPeer::NORMALIZED_TAG.’) AS count
FROM ‘.QuestionTagPeer::TABLE_NAME.’
GROUP BY ‘.QuestionTagPeer::NORMALIZED_TAG.’
ORDER BY count DESC’;

$stmt = $con->prepareStatement($query);
$stmt->setLimit($max);
$rs = $stmt->executeQuery();
$max_popularity = 0;
while ($rs->next())
{
if (!$max_popularity)
{
$max_popularity = $rs->getInt(’count’);
}

$tags[$rs->getString(’tag’)] = floor(($rs->getInt(’count’) / $max_popularity * 3) + 1);
}

ksort($tags);

return $tags;
}

We limit the number of popularity degrees to 4, because otherwise the tag cloud would become unreadable. The result of the method is an associative array of tag names and popularity. We are ready to display it.

我们把流行级别限制到4,因为如果不这样的话,标签阴影就没法看了。函数结果是用标签名和流行程度组成的联合数组。我们来显示一下。

Display a tag bubble

显示冒泡排序标签

Create a simple popular action in the tag module:

创建标签模块的简单popular动作:

public function executePopular()
{
$this->tags = QuestionTagPeer::getPopularTags(sfConfig::get('app_tag_cloud_max'));
}

Nearly as simple as the action is the popularSuccess.php template:

popularSuccess.php模板几乎和动作一样简单:

<h1>popular tags</h1>

<ul id=”tag_cloud”>
<?php foreach($tags as $tag => $count): ?>
<li class=”tag_popularity_<?php echo $count ?>”><?php echo link_to($tag, ‘@tag?tag=’.$tag, ‘rel=tag’) ?></li>
<?php endforeach; ?>
</ul>

Don’t forget to add a routing rule for this new action in the routing.yml configuration file:

别忘了给新动作在routing.yml中加路由规则:

popular_tags:
url: /popular_tags
param: { module: tag, action: popular }

And the app_tag_cloud_max parameter in the application app.yml:

在app.yml里加app_tag_cloud_max参数:

all:
tag:
cloud_max: 40

Everything is ready: display the tag cloud by requesting

完事具备:显示标签阴影效果

http://askeet/popular_tags

Style the tag list items

标签的样式

But where is the cloud? All that the action returns is a list of tags, in alphabetical order. The real shaping is done by a stylesheet, as recommended by web standards. Append the following declarations to the main.css stylesheet (located in askeet/web/css).

但是阴影效果在哪里呢?动作返回一列标签,按字母排序。真正的效果要靠样式表实现。把这些加到main.css里(askeet/web/css)。

ul#tag_cloud
{
list-style: none;
}

ul#tag_cloud li
{
list-style: none;
display: inline;
}

ul#tag_cloud li.tag_popularity_1
{
font-size: 60%;
}

ul#tag_cloud li.tag_popularity_2
{
font-size: 100%;
}

ul#tag_cloud li.tag_popularity_3
{
font-size: 130%;
}

ul#tag_cloud li.tag_popularity_4
{
font-size: 160%;
}

Refresh the popular tags page, and voila!

刷新标签页看看。

wwwsymfony-projectcom__tag_cloud.gif

Post a Comment