Blog

  • Stupid IE6

    我负责的一个网站的访问者中, IE6 用户仍占 6.2%,这个比例不尴不尬。改版初期对他们照顾不够,今天下狠心花了时间去测试 IE6 下的兼容性。几小时之后,IE6 下都能完全正常显示了,我也多了一些心得。

    1. 如果要加层,先设置该层 text-align:left,否则 IE6 下与层有点关系的元素,不管它是在层内还是层外,位置会摆置得莫名其妙。特别是当看到层外元素整体摆错位置,我更莫名其妙,实在搞不懂 IE6 对层的理解是什么。如果一开始就对层进行初始化({ text-align:left; position:relative; })就会节省很多时间。
    2. 避免使用高级的 css 语法,比如多 class 的连贯,.class-a.class-b { color:red; }。本希望该样式只应用于同时具有 class-a 和 class-b 元素,结果 IE6 不理解。如果 IE6 对不理解的 selector 作忽略处理倒还好,可是 IE6 把该样式应用到了所有 class-b 元素(只具有 class-a、不具有 class-b 的元素未受影响),IE6 是不是显得不懂装懂?祸害很大,害我多花了一小时在找被错误影响了的 class-b 元素的原因。如果实在想要多 class 的连贯的 selector,那比较可行的办法是用 jquery 来保证跨浏览器的兼容性,即 $jquery(‘.class-a.class-b’) 。

    题外话:很久前我写过一篇Cross-browser CSS。今天多了两点心得,本想直接写入以前的文章。转念一想不对,今天的心得是今天的,以前的心得是以前的,以后万一我来翻看自己的文章,我也想从中看出我的成长历程,混在一起就看不出历程了。但从心得角度讲,最好把所有 cross-browser css 的内容写在一起,否则,若干年后我再看Cross-browser CSS那一页时不一定想起今天补充的两点。似乎没什么好办法(即使用 related posts 也比较勉强)。

  • Use zoom:1 as a generic IE css fix

    我用“拉门”的方法做了个两端圆弧背景,宽度不固定的按钮,简言之就是用两个嵌套的 span,应用了以下 css

    
    span { background:url(round-corner.gif) 0 50% no-repeat; padding:0 0 0 12px; }
    span span { background-position:100% 50%; padding:0 12px 0 0; }
    
    

    但是在 IE6 下,按钮左边界是圆弧,右边界仍是直角。如果我在 span 上增加 width:100%,左右倒都是圆弧,但由于 IE6 对 width 的定义不同于其他浏览器,按钮的宽度被改变了。

    在没有 width 可用的情况下,怎么办?用

    
    span {zoom:1; }
    
    

    延伸阅读的话,在 IE6 下某些元素没有 layout,用 {zoom:1; } 可以让这些没有 layout 的元素表现得象有 layout (我这么表述可能不准确),从而改善低版本 IE 对 css 标准的理解。{zoom:1; } 是 IE fix 的一个捷径,目前没有发现对其他浏览器带来副作用。

  • Magento 1.4 is not showing out of stock items in product list

    我用过 Magento 1.3。在 Magento 1.3 里,某产品脱销了,仍在前台出现,只是提示 out of stock。

    这其实很好,符合我的 business logic: 脱销总是暂时的,我不希望顾客在这段时间找不到这个产品而误以为该产品 discontinued。如果真遇到产品 discontinued,由 admin 在后台 disable product。Enable / disable 一个产品很方便,这样 admin 会感觉 everything is in control。

    可是 Magento 1.4 起,自动把脱销的产品从前台产品列表里过滤了。这个逻辑搞得我莫名其妙,一开始还以为是我搞错了设置或被我改动过的代码写错了,最后才发现 Magento 1.4 把 getLoadedProductCollection() 的返回值修改了。

    其实这个修改很糟糕,显然 Magento 1.4 在这点上考虑不成熟:如果顾客都找不到这个产品了,那 wishlist 还有用吗?再看 list.phtml 里

    
    <?php $_productCollection=$this->getLoadedProductCollection() ?>
    <?php if(!$_productCollection->count()): ?>
    <p><?php echo $this->__('There are no products matching the selection.') ?></p>
    <?php else: ?>
    <div>
    <?php echo $this->getToolbarHtml() ?>
    <?php // List mode ?>
    <?php if($this->getMode()!='grid'): ?>
    <?php $_iterator = 0; ?>
    <ol id="products-list">
    <?php foreach ($_productCollection as $_product): ?>
    
    ...
    
    <?php if($_product->isSaleable()): ?>
    <p><button type="button" title="<?php echo $this->__('Add to Cart') ?>" onclick="setLocation('<?php echo $this->getAddToCartUrl($_product) ?>')"><span><span><?php echo $this->__('Add to Cart') ?></span></span></button></p>
    <?php else: ?>
    <p><span><?php echo $this->__('Out of stock') ?></span></p>
    <?php endif; ?>
    
    

    $_productCollection 都过滤了脱销的产品,后面的 $_product->isSaleable() 岂不是多此一举?

    我还是得把 getLoadedProductCollection() 按 Magento 1.3 修改回去。

    2010 年 5 月 25 日更新:当我发现 Magento 把显示产品的逻辑从“默认包括 out of stock” 改为 “默认不包括 out of stock”,我想 Magento 应该会在后台提供一个修改默认值的地方。前几天我找了,没找到,所以抱怨说 Magento 自说自话地修改逻辑,其实我冤枉 Magento 了。

    今天我无意中发现这个默认值是可以被修改的,在 System configuration 的 Inventory tab。Inventory 的设置在全局生效,当选择 storeview 时是看不到 Inventory tab 的,所以当时我没发现它。

    Display out of stock products in Magento 1.4
    Display out of stock products in Magento 1.4
  • What happens when an order is cancelled in Magento?

    Order Status

    When you click Cancel button in an order view, order status changes to Cancelled if payment is not received (i.e. invoice is not raised).

    If payment is received before you click Cancel button, order status changes to Complete. You must raise a credit note to offset the payment, then the status changes to Closed. However, this order still counts in dashboard. This statistical result is not what I am expecting, but I think there is a reason inside.

    See table as summary

    Is payment received? No Yes
    Order status changes to >>>Cancelled >>>Complete (before credit note is raised)

    >>>Closed (after credit note is raised)

    What is order status when payment or credit is partially done?

    If an order is cancelled when payment is partially received, the status changes to Complete immediately as if full payment is received.

    If a Complete order is partially refunded, the status remains Complete. The status changes to Closed when customer is credited in full.

    See table as summary

    Payment is partially received >>>Complete (before credit note is raised)

    >>>Complete (credit noted is raised but partially)

    >>>Closed (after full refund)

    Will inventory level reverse itself when an order is cancelled? It depends.

    Reversal only happens if this order is not shipped AND payment is not received.

    See table as summary

    Order is not shipped

    AND

    payment is not received

    Order is shipped

    OR

    payment is received

    Inventory level Reversal No reversal on cancelling

    Will inventory level reverse itself when an order is cancelled after it is partially fulfilled? And how much is the reversed inventory?

    If an order is partially fulfilled (payment is partially received or shipment is partial), there are 3 types of quantity, Qty Ordered, Qty Invoiced, Qty Shipped, showing at order view. Then you click Cancel button, you get –

    Qty Cancelled = Qty Ordered – max (Qty Invoiced, Qty Shipped)

    Qty Cancelled is the quantity automatically reserved to inventory.

  • I like Meng Fei

    孟非应该出名很久了吧,只是我最近才看《非诚勿扰》才知道他。一看就很喜欢他的台型和主持风格,觉得他反应很快、用词都很恰当,对我胃口。

    对我胃口,仅此而已。但最近一期他在节目中即情引用了法国哲学家狄德罗说的“在剧场、也只有在剧场,好人和恶人的眼泪才会流到一起”(我 Google 了一下,他的话与原文略有出入,但不影响理解),让我对他刮目相看。看来孟非老师不是白叫的。

  • No formatting inside html tag

    我目前用的 WordPress 版本为 2.8.4。发现一个 bug,就是在 post html 源码编辑状态(非 Visual 编辑),输入时为

    <div>
    我希望 WordPress 自动格式化post时不要自说自话把html code加进html tag里。
    
    不知 WordPress 能听明白吗?
    </div>
    

    保存后查看一下数据库,post_content 忠于原文,前台输出时却经过 WordPress 的格式转换,变成了

    
    <div>
    我希望 WordPress 自动格式化post时不要自说自话把html code加进html tag里。</p>
    <p>不知 WordPress 能听明白吗?
    </p></div>
    
    

    html p tag 都不配对了。

    这个 bug 不严重,因为 WordPress 自带的 TinyMCE editor 不会插入 div tag。TinyMCE 倒可以插入 b-quote,输入时

    <blockquote>
    我希望 WordPress 自动格式化post时不要自说自话把html code加进html tag里。
    
    不知 WordPress 能听明白吗?
    </blockquote>
    
    

    前台输出

    <blockquote><p>
    我希望 WordPress 自动格式化post时不要自说自话把html code加进html tag里。</p>
    <p>不知 WordPress 能听明白吗?
    </p></blockquote>
    
    

    完全正确,没有触发 bug。这个小 bug 让我联想到很多 wysiwyg editor 和 output formatting 时不尽如人意的地方。

    我在 magento 和 phplist 里都是禁用 wysiwyg editor 的,在 WordPress 里虽没有禁用它,但我有意识地仅仅使用它的 htmlentity。象我习惯于手写 html 代码以求更多输出控制的人,觉得 WordPress output formatting logic 也不好,更好的逻辑是:

    • 如果post_content是有效的DOM,仅对最外层的 text nodes 作 html p wrapping,对任何 html tag 内部的不做任何处理。其他诸如 htmlentity filter 都不需要,因为 wysiwyg editor 已经作了这项工作。
    • 如果post_content是无效的DOM,WordPress 默认的 formatting 会作一些修复。我没考察过修复的智能程度,我的要求是一开始就不要写出无效的 DOM,所以不想多讨论这种情况下的处理。

    举个例子说,我的 formatting logic 就是把

    anything outside html tag is wrapped by p tag.
    
    do not touch anything inside html. user should be responsible for it.
    <div>
    我希望 WordPress 自动格式化post时不要自说自话把html code加进html tag里。
    
    不知 WordPress 能听明白吗?
    </div>
    

    输出为

    <p>anything outside html tag is wrapped by p tag
    </p><p>
    do not touch anything inside html. user should be responsible for it.</p>
    <div>
    我希望 WordPress 自动格式化post时不要自说自话把html code加进html tag里。
    
    不知 WordPress 能听明白吗?
    </div>
    
  • UK airports postcode

    Gatwick

    • South Terminal: RH6 0NP
    • North Terminal: RH6 0PJ

    Heathrow

    • Terminal 1: TW6 1AP
    • Terminal 2: Closed until 2014.
    • Terminal 3: TW6 1QG
    • Terminal 4: TW6 3XA
    • Terminal 5: TW6 2GA

    BAA Heathrow 网站上把整个 Terminal 2 版块都下线了,暂查不到 postcode。我认为在 Terminal 2 关闭期间,应该保留 Terminal 2 版块,只是在显著位置提示该 Terminal 处于关闭状态。我原本不知道 Terminal 2 处于关闭状态,因为在 BAA Heathrow 网站上根本找不到 Terminal 2 版块,我去了其他的新闻站点(其实是去年的旧闻了)才知道这条消息。

    虽然各个机场的 postcode 很容易查到,但每去一次机场都 google 一次然后在 airport website 上翻好几页找 postcode 也挺烦,干脆我就在自己的空间记个笔记。

  • Email notification best practice

    我见很多购物网站花很多力气组织语言,把邮件正文用日常交流的通顺的口气写出来,以为顾客会喜欢读这样的邮件,但我认为事与愿违。在快节奏的社会里,大家阅读任何邮件时已经习惯最多扫一眼,通知邮件类似于营销邮件,能得到顾客一眼的停留亦属非常奢侈。

    比如,货物发运通知:

    xxx先生,

    我很高兴地通知您,你的订单xxx号的货物已交由xxx物流公司隔天送达。订单项下xxx商品预定数量xxx件,因库存不足,此批发送xxx件,余下的xxx件将于xxx天后发出。

    谢谢惠顾!

    通知中有很多重要信息,如果写进大段的文章,客人skim阅读时认为这是“你好我好大家好”式的寒暄,往往被忽略。因此,通知还不如写成bullet mark style:

    • 订单号:xxx
    • 物流公司:xxx
    • 预计抵达:隔天
    • 预定数量:xxx件
    • 已发:xxx件
    • back order: xxx件
    • back order ETA: xxx天

    当然,以上纯bullet mark格式是另一个极端。在实战中,应以bullet mark展示重要信息为基本原则,适当组织可读的礼貌用语,通知邮件才会有理想的效果。

  • Understanding layout.xml syntax in Magento

    I write this article about my understanding of layout.xml up to now. I use it to show you

    • How to add buttons in order/invoice/shipment/creditmemo without override Block class?
    • How to define an array as a parameter in layout.xml using <action method=”addButton”><param>array</param></action>?
    • How to assign any type of variables in layout.xml?
    • How to avoid overriding controller class but still get the result you want on a specific url?

    as well.

    All my understanding was inspired by the task: adding buttons in order view at backend to offer administrators extra order process facilities.

    add-buttons-to-backend-order-view

    I love Magento class overriding architecture, but as I have learned the downside of overriding (Basically, there is only one chance of overriding for each class among all extension classes. Therefore, the more classes overridden, the greater chance of conflict between extensions.), I create a principle for my programming in Magento – do not override Magento class unless there is no other way around it.

    So, back to the task. It would be very easy to add the button if I had overridden Mage_Adminhtml_Block_Sales_Order_View. It could be

    public function __construct() {
    parent::__construct();
    $this->_addButton('new_button', array(
    'label'    => Mage::helper('sales')->__('New Button'),
    'onclick'  => "window.open('{$this->getUrlForNewButton()}')",
    ))
    ;
    }
    

    However, I do not want to override Mage_Adminhtml_Block_Sales_Order_View. How about firing addButton method in layout.xml? I see an example of addTab at the left side

    <block type="adminhtml/sales_order_view_tabs" name="sales_order_tabs">
    <action method="addTab"><name>order_info</name><block>order_tab_info</block></action>
    <action method="addTab"><name>order_invoices</name><block>adminhtml/sales_order_view_tab_invoices</block></action>
    <action method="addTab"><name>order_creditmemos</name><block>adminhtml/sales_order_view_tab_creditmemos</block></action>
    <action method="addTab"><name>order_shipments</name><block>adminhtml/sales_order_view_tab_shipments</block></action>
    <action method="addTab"><name>order_history</name><block>adminhtml/sales_order_view_tab_history</block></action>
    </block>
    
    

    In the example, addTab method takes two parameters, one for tab name and the other for block name. Inside addTab method, it creates a block object from the block name, which is passed in as a string. In fact, the tag name <name> or <block> is not significant in layout.xml. It does not represent parameter name in method. But the position is significant – the first tag is passed into method as the first parameter, and ditto the followings.

    Back to addButton method in Mage_Adminhtml_Block_Widget_Container. It takes at least two parameters, one as string and the second as associated array. So the layout.xml can be something like this:

    
    <reference name="sales_order_edit">
    <action method="addButton" translate="label">
    <name>new_button</name>
    <button_array><!-- this tag name is not significant -->
    <label>New Button</label>
    <onclick>window.open('{$this->getUrlForNewButton()}')</onclick>
    </button_array>
    </action>
    </reference>
    
    

    The difficult part is in <onclick> – it requires a variable of current order id. After some digging, I find another example:

    
    <reference name="footer_links">
    <action method="addLink" translate="label title" module="catalog" ifconfig="catalog/seo/site_map"><label>Site Map</label><url helper="catalog/map/getCategoryUrl" /><title>Site Map</title></action>
    </reference>
    

    So, I create a helper method to return url for new button and modify layout.xml to

    
    <reference name="sales_order_edit">
    <action method="addButton" translate="label">
    <name>new_button</name>
    <button_array><!-- this tag name is not significant -->
    <label>New Button</label>
    <onclick helper="mymodule/getUrlForNewButton"/>
    </button_array>
    </action>
    </reference>
    

    Unfortunately, it did not work. I assume the helper callback does not work if it is in the array. So I change the helper method to return an associated array directly as the second parameter for layout.xml and the final layout.xml is

    
    <reference name="sales_order_edit">
    <action method="addButton" translate="label">
    <name>new_button</name>
    <button helper="mymodule/getNewButtonSecondParam"/>
    </action>
    </reference>
    
    

    It works perfectly. I am very proud of myself.

    Some while ago, I overrode Mage_Catalog_ProductController just to register a variable in viewAction

    
    public function viewAction() {
    Mage::register('my_var', $value);
    parent::viewAction();
    }
    
    

    Now I reviewing that approach stupid. I could register a variable using layout.xml without any class overriding!

  • cp -f does not switch off prompt?

    我在 Fedora 下执行大量的文件拷贝操作,我使用了

    cp -fr [SOURCE] [DEST]

    我使用了 -f 开关就是不想被询问文件是否覆盖。但

    cp: overwrite `(filename)’? 提示还是出现。

    这是什么原因?原来 Fedora 带有几个 alias,可以用 alias 命名查看。

    # alias
    alias cp=’cp -i’
    alias l.=’ls -d .* –color=auto’
    alias ll=’ls -l –color=auto’
    alias ls=’ls –color=auto’
    alias mv=’mv -i’
    alias rm=’rm -i’
    alias which=’alias | /usr/bin/which –tty-only –read-alias –show-dot –show-tilde’

    cp 被 alias 了!-i switch 优先于 -f,所以 cp -fr [SOURCE] [DEST] 其实是执行了

    cp -ir [SOURCE] [DEST]

    怎么才能让 overwrite 提示不出现呢?办法有二:

    办法一是使用 unalias 解开 cp -i。但 cp 默认使用 -i 确实很不错,我不想放弃 Fedora 的体贴。于是——

    办法二是使用
    /bin/cp -ir [SOURCE] [DEST]