Drupal 7:PHP输出的页面多出空行的问题

最近接到一个活,是用Drupal7搭一个网站,实现简单的分角色发布文章的功能,架构是XAMPP+PHP+MySQL+Drupal7。 以下是我在实际项目中遇到的一些问题和解决办法。

问题

我下载了一个Drupal 7的主题,然后覆写它的模板,以实现自定义布局。 覆写完以后发现,页面上方会多出一栏空行。

正常的网页:

image1

不正常的网页,顶部多了一行空行:

image2

探究

F12审查元素看看:

image3

这让我百思不得其解啊,拿修改前的和修改后的文件一行一行的对比,完全没有问题啊!

image4

然后突然看到了的底下状态栏,两个文件好像不一样:

image5

解决

UTF-8 with BOM是什么东西?试着改回成UTF-8试试,改回来果然就好了!

原因

百度了一下,UTF-8 BOM:

BOM——Byte Order Mark,就是字节序标记

在UCS 编码中有一个叫做”ZERO WIDTH NO-BREAK SPACE”的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输 字符”ZERO WIDTH NO-BREAK SPACE”。这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little- Endian的。因此字符”ZERO WIDTH NO-BREAK SPACE”又被称作BOM。

UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符”ZERO WIDTH NO-BREAK SPACE”的UTF-8编码是EF BB BF。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。

UTF- 8编码的文件中,BOM占三个字节。如果用记事本把一个文本文件另存为UTF-8编码方式的话,用UE打开这个文件,切换到十六进制编辑状态就可以看到开 头的FFFE了。这是个标识UTF-8编码文件的好办法,软件通过BOM来识别这个文件是否是UTF-8编码,很多软件还要求读入的文件必须带BOM。可是,还是有很多软件不能识别BOM。

在Firefox早期的版本里,扩展是不能有BOM的,不过Firefox 1.5以后的版本已经开始支持BOM了。现在又发现,PHP也不支持BOM。PHP在设计时就没有考虑BOM的问题,也就是说他不会忽略UTF-8编码的文件开头BOM的那三个字符。 ~~~

果然,PHP是不支持BOM的,会把BOM头显示成一个空字符。

为什么我会把修改过的文件保存成有BOM的UTF-8呢?

想到项目刚开始的时候我用的是Notepad++,后来用的是Sublime Text 3,会不会是编辑器的问题?

果不其然,Notepad++的保存格式是这样的:

image6

Sublime Text 3的保存格式是这样的:

image7

摔!两个编辑器默认的UTF-8根本不是一个东西啊!

从此成了Notepad++一生黑!!!

jQuery Mobile:JS绑定的按钮动作不正确的问题

在项目过程中,需要一组图片设置长按响应:长按弹出一个对话框,点击确认删掉当前图片,然后刷新页面。

在最开始的时候,我是这样写的:

for(i = 0; i < bookNum; i++){
    (function(){
        var index = i;
        var bookHref = $('a#book_'+index);
        bookHref.bind("taphold", {index: index}, tapholdHandler);
    })();
}

function tapholdHandler(event) {
    $("#dialogDelete").popup("open");
    var dialogCommit = $('#dialogCommit');
    dialogCommit.click(function(){
        //删除书,然后刷新页面。
    });
    $('#dialogCancel').click(function(){
        $("#dialogDelete").popup("close");
    });
}

然后发现,第一次长按删除的时候是正常的,第二次开始就乱七八糟的,多删了很多东西。

通过各种输出log调试,发现删除第一张图片的时候正常,删除第二张图片的时候会连续删两张,删除第三张图片的时候会连续删三张……

探究

这就让我有点怀疑,是不是每次刷新页面的时候注册监听的问题?

百度了一下,果然如此,跟Java不同,JavaScript给界面元素绑定事件并不会替换原来绑定过的事件,而是会重复绑定。

还是上一节说的问题,Java的机制是“设置监听”,JavaScript的机制是“绑定事件”。 明白这一点后就好解决了。因为我删掉一张图片以后会刷新一下页面,即会重复绑定一次。

解决

解决办法就是在每次bind()绑定之前,先用unbind()解绑一次。

for(i = 0; i < bookNum; i++){
    (function(){
        var index = i;
        var bookHref = $('a#book_'+index);
        bookHref.unbind("taphold",tapholdHandler);
        bookHref.bind("taphold", {nid: json_books[index].nid},tapholdHandler);
    })();
}
function tapholdHandler(event) {
    $("#dialogDelete").popup("open");
    var dialogCommit = $('#dialogCommit');
    dialogCommit.unbind("click");
    dialogCommit.click(function(){
        //删除书,然后刷新页面
    });
    $('#dialogCancel').click(function(){
        $("#dialogDelete").popup("close");
    });
}
jQuery Mobile:JS批量设置按钮动作的问题

刚刚接触JavaScript,用的是JQuery Mobile框架,很多东西还不熟悉,写的代码难免会想当然得用写Java和Android时的惯性思维。

问题

今天遇到一个问题,我动态生成了若干个按钮,id命名为Comment_1Comment_2……在设置按钮监听的时候,由于按钮总数是无法事先知道的,所以不可能为每一个按钮写一个监听,于是我用了循环来重复设置监听,代码如下:

for(i = 0; i < commentNum; i++){
    $('a#Comment_'+i).click(function(){
        sessionStorage.curComment = comments[i].cid;
        $.mobile.changePage("commentInfo.html");
    });
}

然而,在测试代码的时候发现,第3行一直报错:

Uncaught TypeError: Cannot read property 'cid' of undefined

comments[i]未定义。在中间加了一个log输出i,发现点击任何一个按钮,i的值都是commentNum

这种写法在Java和Android里是正确的,但是为什么在JS里不对了呢?

探究

Google了一下,大致明白了一点点。

在Java中,UI的界面事件(比如按钮按下、触摸、滑动)是由虚拟机进行监听的,监听到了点击动作,就一层一层向下传递,直到被按钮接收到,然后进行处理。在Java/Android中这个监听是需要提前向虚拟机进行“注册”的,下面是Android中典型的循环设置监听的代码:

Button[] buttons = {btn1,btn2,btn3,btn4,btn5};
for(int i = 0; i < buttons.length; i++){
    buttons[i].setOnClickListener(new OnClickListener(){
        @Override
        public void onClick(View v) {
            //do something
        }
    });
}

这段代码在UI界面生成完毕前运行,for循环每循环一次,buttons数组中就有一个按钮都被设置了监听,直到全部设置完毕。UI界面生成完毕后,用户点击每个按钮就会触发对应的动作。

然而,与Java不同,JavaScript并没有类似的监听机制,而使用的是看上去很类似的“事件绑定”机制。当一个按钮被按下的时候,click()内的代码才会被执行,而一般用户按下按钮的时候,js内的代码都已经执行完毕了,For循环也早已经循环完毕,这个时候i的值为CommentNum,执行comments[i]就会报错——i超过了数组下标范围。

解决

那么怎么才能给这些按钮设置监听/绑定呢?如果我们仍然要用For循环进行绑定的话,可以使用以下代码:

for(i = 0; i < commentNum; i++){
    (function(){
        var index = i;
        $('a#Comment_'+index).click(function(){
            sessionStorage.curComment = comments[i].cid;
            $.mobile.changePage("commentInfo.html");
        });
    })();
}

这里利用了JS的闭包原理,每次循环都会新建一个闭包,里面的变量值(在这里是index的值)在运行完毕后并不会被销毁,而是一直保存(参考:JS闭包的作用原理),当你按下按钮时,click()被触发,这个时候index就是其对于的id号了。

PS:其实并不推荐这样使用循环绑定事件,过多的闭包会影响性能,最好是用js的事件冒泡机制来实现,有兴趣的可以研究一下。

Android:解决PullToRefresh的setOnTouchListener()无效的问题

如果直接给PullToRefreshListView设置OnTouch(),会发现没有反应:这个函数根本没有被调用。

我之前的篇文章探讨过PullToRefresh的实质:PullToRefresh如何滚动到最顶部

想要给ListView设置setOnTouchListener(),直接给PullToRefreshListView设置是没有用的,要使用:

listView.getRefreshableView()
	.setOnTouchListener(new OnTouchListener(){...});

至于为什么在PullToRefreshListViewsetOnTouchListener()里放Log都不显示(根本没调用),

这个问题仍然值得探讨,如果有人有答案,欢迎留言。

Android:PullToRefresh如何滚动到最顶部

一般如果用ListView,让它滚动到顶部,是这样写的:

if (!listView.isStackFromBottom()) {
    listView.setStackFromBottom(true);
}
listView.setStackFromBottom(false);

但是,使用PullToRefreshListView以后,发现该对象竟然没有setStackFromBottom()方法!

探究

翻翻它的源码,发现是这样的:

public class PullToRefreshListView extends PullToRefreshAdapterViewBase<ListView>{...}

它并不是继承于ListView,所以也无法将这个对象castListView

但是,实际上PullToRefreshListView的主体确实是一个ListView,那么如何使用属于ListView的方法呢?

原因

Google了半天,终于在stackoverflow上找到了答案:Retaining scroll position on Pull To Refresh

PullToRefresh为了实现各种不同的View的下拉刷新,并不是简单的继承自ListView,而是采用了泛型。

实际上可以理解为在ListView(或者其他想要实现下拉刷新的View)外面包了一层ParentView

想要得到里面的ListView,有这样一个方法:

listView.getRefreshableView();

解决

因此,想要让它回到顶部,代码如下:

ListView mlist = listView.getRefreshableView();
if (!(mlist).isStackFromBottom()) {
    mlist.setStackFromBottom(true);
}
mlist.setStackFromBottom(false);

解决问题!