Morris.js的性能不行,原本用Google Chart很容易画出来的图表用Morris直接就把Chrome搞死了……
偏偏我移植到最后才发现……蛋疼啊……只要继续混用了……啊……
Morris.js的性能不行,原本用Google Chart很容易画出来的图表用Morris直接就把Chrome搞死了……
偏偏我移植到最后才发现……蛋疼啊……只要继续混用了……啊……
尼玛折腾了一早上,居然这样写会导致事件无法触发:
var script = document.createElement('script');
script.src = 'somefile.js';
script.onload = function () { console.log('xx'); };
$('head').append(script);
最后一句换成纯js就解决了:
document.head.appendChild(script);
![[教程]纯CSS实现多选组件](https://blog.meathill.com/wp-content/uploads/2013/10/IMG_0678-1200x896.jpg)
在我们的后台中,需要设置广告精准投放的区域,也就是要在全国31个省、自治区、直辖市中选择。那么,出现下面这幅景象也就理所应当了:

这样做有几个问题:
于是我想,首先应该把所有选项分为“已选中”和“未选中”两批,解决第2个问题,减轻第3个问题;其次复选框本身的价值不大,可以被替换为其它样式;唯一可能引入的问题,就是点选时,用户的预期是看到复选框里出现一个小对勾,表示选中,如果我把它移开放到“已选中”组里,用户可能会迷惑,需要一些时间学习。
于是我跟某产品经理朋友聊了聊这个想法,他表示确实可能造成用户迷惑,不过如果能加入动画效果,那么基本没问题。嗯,开始动手。
近日flexbox规范定案,浏览器相继支持display:flex;,同时传来一条好消息,新实现比老实现display:box;快很多。这次我打算用flexbox来解决问题,因为里面有一个很重要的属性:order(之前叫box-ordinal-group),它可以改变布局中元素的排列顺序,配合CSS3新增的选择器,应该可以满足需要。
(关于flexbox的知识,可以通过Google了解,虽然搜到的多是上一个版本,不过和最终版差别不大,只是叫法不同。本文不再过多讲解,我就当大家都会了)
<input type="checkbox">本身的样式不能修改,所以我们必须借助<label>的帮助;实现选中/未选中区分,那自然就要用到伪类:checked;选择器一定是从外到内、从前到后的,没法选择父级元素,所以不能用<label>去包<input>,那么最终布局就只能是:
<div>
<input type="checkbox" name="q[]" id="q1" />
<label for="q1">小宝3225</label>
<input type="checkbox" name="q[]" id="q2" />
<label for="q2">王老白白白</label>
<input type="checkbox" name="q[]" id="q3" />
<label for="q3">空夫31</label>
<input type="checkbox" name="q[]" id="q4" />
<label for="q4">谷大白话</label>
<input type="checkbox" name="q[]" id="q5" />
<label for="q5">Meathill</label>
<input type="checkbox" name="q[]" id="q6" />
<label for="q6">一毛不拔大师</label>
</div>
很简单哈,不解释了。CSS3新增了“下一节点”选择器 +,用来选择某节点的下一个节点,结合:checked伪类就可以将选中的<input>和它临近的<label>通过改变order属性移到前面去:
#container {
display:flex;
flex-direction:row;
flex-wrap:wrap;
}
#container input,
#container label {
order: 2; //所有选项、label顺序为2
}
input[type=checkbox]:checked,
input[type=checkbox]:checked + label {
order: 0; // 越小越靠前
}
不过这样只是把选中的内容提前,视觉上没有真正的分割。所以我决定再加入一根分割线,上面是选中的,下面是未选的。这个时候我们需要用到 ~ 这个选择器,选择某节点后面的节点:
hr {
display:none; // 默认情况下,没选任何选项,分割线隐藏
order: 1; // 分割线顺序为1
width:100%; // 保证独霸一行
}
input[type=checkbox]:checked ~ hr {
display:block; // 有选项被选中后才会显示分割线
}
Demo如下:
这样基础功能实现了。不过视觉上,排版仍然不整齐,选中的选项和未选中的选项区分不算太明显,所以下一步我准备美化下checkbox。
做法与前面类似,也要用到CSS3新增的选择器。前面为了实现<label>提前,没有用它包裹<input>,所以在选项很多很长导致换行的时候,可能出现复选框和标签脱离的尴尬状况。好在复选框的价值可以用别的样式取代,所以先把小方框隐藏起来,转而将<label>作为操作目标,再来点边框底色圆角(参考自Bootstrap 3),就可以了:
input[type=checkbox] {
display: none;
}
label {
min-width: 120px;
border: 1px solid #CCC;
padding: 2px 8px;
text-align: center;
margin: 0 5px 5px 0;
background: #FFF;
color: #333;
border-radius: 3px;
box-sizing: border-box;
}
label:hover {
border-color: #ADADAD;
background: #EBEBEB;
cursor: pointer;
}
input[type=checkbox]:checked + label {
order: 0;
background-color: #5cb85c;
border-color: #4cae4c;
color: #FFF;
}
input[type=checkbox]:checked + label:hover {
background-color: #47a447;
border-color: #398439;
}
这样看起来还有上升空间,如果加上几个图标响应用户操作,那么学习成本会更低,对操作后的预期也会更准确。于是引用CDN上的font-awesome,使用:before伪类加上小图标,就得到了最终效果:
我无意中发现,这样批量添加删除时,鼠标可以常点不动,应该也是个意外的收获吧。
至此功能基本做好了,不过由于修改了行为,可能导致用户迷惑,所以准备加个动画帮助用户理解这个交互。
可惜作为一个新功能,浏览器的支持尚不完善,虽然规范中规定“animatable: yes”,但是实测在Chrome v.30也无法工作:http://jsfiddle.net/meathill/Ka66W/1/
看来只有等新版浏览器发布后再去完善了。
使用纯CSS做组件,几乎不用担心兼容性问题,因为浏览器本身就做了很好的向下兼容,代码最多不生效,一般不会错。
具体到这个组件,因为只针对视觉效果,没有增删改任何浏览器行为,所以兼容性也没有任何问题。不过最终效果呢,只有支持flexbox和CSS3选择符的浏览器才能正常渲染。
我的环境是Window 8 + Chrome v.30,以及小米2 + Chrome v.30,测试通过。
如今CSS很强,纯CSS可以实现很多功能,希望今后能做出更多有价值的东西。分享这个组件的实现,希望对大家有用。
上周GFW又抽风,导致取自CDN的jQuery和Bootstrap经常404,后台各种罢工。
开始想说干脆放弃CDN得了,结果自家服务器也不是很给力……本地路径的静态文件也经常加载失败,挠了半天头,再去找找国内的CDN吧。
后来想起来前几天看到七牛搞了个免费的开源仓库CDN,通过Google找到,叫http://www.staticfile.org/。打开一看,首页只列出不多的几个库,版本也不是最新的。我以为又是个没人维护的烂尾工程,读了介绍才知道他们倡导大家都来提交库信息,共同建立全面的CDN资源。我本想把这次要到的库和可以更新的库提交上去,后来发现原来他们已经引入了cdnjs.com里所有的库,只不过没有写在首页……果然大家都喜欢写代码不喜欢写文档啊,差点就错过了。
BTW,cdnjs.com居然还提供了animate.css,真好。
好在他们做了命令行工具,可以装上查引用地址。比如我想知道能不能用underscore,就可以这样:
// 安装 npm install -g sfile // 查找underscore sfile search underscore // 得到链接,这里要用全名 sfile get underscore.js
最后看https://github.com/staticfile/static/issues里的内容才知道,他们会把国外成熟的库直接从cdnjs复制过来,提交新库应以国内的为主。嗯,将来把Nervenet弄完也提交进去。
重复一下网站地址:http://www.staticfile.org/。最后感谢下这些好人,以及服务提供商七牛云存储。

最近接到这么个需求,要把 <table> 显示的数据导出成 Excel。类似的需求并不稀罕,过去我通常用 PHP 输出 .csv 文件。不过这次似乎不太合适:作为数据源的表格允许用户有一些筛选和排序的动作,与原始数据显示有区别,传递操作比较麻烦;另外 .csv 文件的功能受限严重,难以扩展。所以我准备尝试下别的做法。
Google之,发现 HTML5 又成了一座分水岭。之前在IE浏览器下,用户可以利用 ActiveXObject 创建 Excel.application 对象来处理。后来 Excel 开放标准,可以导出 xml 格式的文件,dataURI 就有了用武之地,导出 <table> 数据并保存为 Excel 有了更好的选择。
(以下内容与 StackOverflow中的答案有重合,那个3条赞同的我认为是最佳答案,可惜我没法顶他……)
或者,直接复制下面一段(这一段我使用了Handlebars模版,以便将来填充数据)
template = ‘<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{{worksheet}}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body>{{#each tables}}<table>{{{this}}}</table>{{/each}}</body></html>';
复制数据比较简单了。如前面模版所示,这里我很野蛮的直接复制 <thead> 和 <tbody> 的全部代码,填充内容。当然为了体现用户操作,我只复制显示的 <tr>。这里需要注意的是,jQuery 判断一个dom 是否处于显示状体基于以下3点:
display:nonetype="hidden"所以,不能先 .clone() 再 .find(':hidde').remove(),因为添加到主 Dom 树之前,节点宽高都是0,也就会被认为还没显示,这下就都干掉了。
套用模版之后,我们就有了完整的表格数据。接下来,我们需要把其转换成 Base64 格式,以便套用 dataURI 输出。于是便要使用 btoa 这个函数(将二进制数据转换成 base64 格式的字符串),不过注意,这个函数不能直接转换普通 unicode 字符,不然大多数浏览器都会抛出异常。所以需要先经过两步转换:
function base64(string) {
return window.btoa(unescape(encodeURIComponent(string)));
}
(MDN 还推荐了 另外一种做法,通过 Typed Array 做中介,我没有实操,有兴趣的可以试下。)
然后配上 data 头和 mimetype,就可以触发下载了:
var uri = 'data:application/vnd.ms-excel;base64,';
location.href = uri + base64(template(tables));
貌似到这里就完成了,不过作为一名挂职产品总监的码农,我很难容忍下载的文件文件名是“下载”,而且还没有扩展名(Windows 8 下,Windows 7 和 Mac 下会有.xls的扩展名,我认为和装的软件注册 mime 类型有关)。
这是个用在内部管理后台的需求,我之前曾要求大家必须使用 Chrome 访问后台;而且我知道,Chrome 已经支持 <a> 里的 download 属性。那么这就好办了,因为 onclick 事件会先于系统默认行为触发,所以我可以在这个事件的处理函数中将生成的 Base64 放在被点击按钮的 href 里,并将其 download 属性设为容易理解的“某年某月末日至某年某月某日广告数据分析.xls”。至此,此项功能宣告圆满。
HTML部分(使用了Bootstrap和Handlebars):
<a href="#" title="点击下载" class="btn btn-primary export-button" download="{{start}}至{{end}}广告数据分析.xls"><i class="icon-download-alt icon-white"></i> 导出</a>
JavaScript 部分
tableToExcel: function (tableList, name) {
var tables = []
, uri = 'data:application/vnd.ms-excel;base64,'
, template = Handlebars.compile('<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{{worksheet}}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body>{{#each tables}}<table>{{{this}}}</table>{{/each}}</body></html>');
for (var i = 0; i < tableList.length; i++) {
tables.push(tableList[i].innerHTML);
}
var data = {
worksheet: name || 'Worksheet',
tables: tables
};
return uri + base64(template(data));
},
exportHandler: function (event) {
var tables = this.$('table')
, table = null;
tables.each(function (i) {
var t = $('<table><thead></thead><tbody></tobdy></table>');
t.find('thead').html(this.tHead.innerHTML);
t.find('tbody').append($(this.tBodies).children(':visible').clone());
t.find('.not-print').remove(); // not-print 是@media print中不会打印的部分
t.find('a').replaceWith(function (i) { // 表格中不再需要的超链接也移除了
return this.innerHTML;
});
table = table ? table.add(t) : t;
});
event.currentTarget.href = Dianjoy.utils.tableToExcel(table, '广告数据');
}
说是圆满,其实也不尽然,因为 URL 有 2M 的长度限制,遇到真正的大表仍然可能出问题(我没实测)。
最后例行吐槽:老板(领导)想提升工作效率,必须考虑员工的日常软件:不许用乱七八糟的浏览器,统一 Chrome;360 一定禁用(最近遇到 N 起升级 Chrome Dev 30 版导致各种 bug 的问题);全部装 Windows 8(自带杀毒,几乎所有外设秒配)。能做到这几点,公司办公效率提升1倍不止。
在上一篇日志中,我介绍了Nervenet的创作思路。虽然JavaScript有着各种各样的先天不足,但是,运气也是实力的一部分,所以广大开发者只有用各种手法去适应它、改良它。应该说大家干得很棒,我也想贡献自己的力量,于是创造了Nervenet,希望解决我在开发中遇到的各种问题。
就在我写完Nervenet初版的时候,偶然看到MVC框架soma.js的介绍,发现跟我的思路很相像(其中用到的IoC类库:infuse.js,也是他们开发的)。于是仔细研究了一番,学到不少东西。今天我准便拿Nervenet和它们分析对比一下。
我自认是个不喜欢“重复发明轮子”的人,于是看到出发点和实现方式如此接近的框架,不免一惊,心说果然世界足够大,持同样想法的人非常多。不知道soma.js的作者有没有用过robotlegs,二者的API真的很像(也许是mvc框架的标配吧,我没看过相关介绍)。我最初也希望引入robotlegs的做法来改善JavaScript编程体验,不过在反复思考后,觉得并不需要全部移植,比如mediator。在Flash里,新的影片剪辑被添加到舞台上时会触发Event.ADDED事件,可以被robotlegs侦听;同时,所有mc都是Sprite的子类,可以使用类名作为索引来创建需要的mediator。而到了JavaScript方面,Dom节点发生变化并不会触发事件;添加的Dom节点也没有类的关系,所以这里的mediator只能我们自行创建,这样其实也就没什么实质性的好处了。
另一个不太需要移植的是Command类。在MVC框架中,它的功能基本就是响应全局事件,进行相应的处理,很多时候只要实现execute方法就好。ActionScript 3在面向方向上做的比较充分,代码都会封装成类,于是Command里还可以放一些helper类型的函数;到了JavaScript这儿就显得不太合适了,既没有强继承关系也没有类型检查,甚至连类的实现都不完整,helper也可以用闭包实现,如果一样搞成类来处理,只是凭空加重了对代码的限制,在我看来有点得不偿失了。
所以我在Nervenet中并没有把robotlegs的功能都移植,而是选取部分比较重要一定会用的实现了。(代码参看测试用例,这里不贴了)
接着再说infuse.js。我一开始准备直接给对象加上app或者context或者injector属性,但是一直觉得这样太过简单粗暴;看过源码发现他们比我略微温柔一点,先遍历对象的属性,如果map了同名属性,就注入进去——仔细想想这差不多是另一种粗暴吧,不由分说的注入同名属性,如果代码不是针对infuse.js写的,可能会产生更多问题。不过我还是学习了这种做法,并进行了一些改造:如果对象属性中有以“$”(可配置)开头的同名属性,就注入。有了这样的规则,新写代码有理可依,改动代码也会比较放心,阅读代码时也有利于识别本身属性和注入属性。
JavaScript没有类型检查,但是在日常开发中难免遇到多个类的实例适合同一个名字,比如model、remote之类的,如果在注入时能自动选择合适的类型,那自然是极好的。于是我想到利用变量声明时的初始值,把类名包括命名空间写进去,作为类型说明,就可以在注入时自动选择合适的类型了。
代码请看测试用例inject部分。
值得一提的是,infuse.js中每个函数都对参数进行了充分的验证,很值得学习,不过我目前还是偷懒只验证了很少一部分。
依赖管理和代码加载也是我力图实现的功能,虽然看起来和架构无关,不过在操作层面上,还是比较合拍的。因为我们总要有一个入口函数,比如jQuery中的$(function () {});,通过分析入口函数,就能得到依赖关系,继而可以实现依赖管理和代码加载,这样丝毫不会影响代码架构。目前我也正是这么做的。
不过这种“自然”的代码书写方式也会给加载带来难度。无论AMD还是CMD,都会把代码以函数的形式封装起来,在依赖处理完成后执行;而这种自然的方式,就要求每段代码执行前依赖都已经加载了,所以只能用Ajax把代码以文本的形式加载下来,分析依赖,继续加载,直至全部完成;在按照依赖关系放入script标签执行。如此一来,执行的代码是不允许依赖关系嵌套的,那么,以闭包来实现私有属性和方法的做法就行不通了。这点我还在思考解决方案。
使用方式参看测试用例。
目前Nervenet已经初步完成,我正在编写入门文档,并将其应用到实际项目中进行测试。这些完成后将发布0.1版。目前市面上有一些做法很接近的框架,不过具体实现上还有差异,孰优孰劣也有待验证。我会尽量解决各种开发中的痛点。

weinre是个开源项目,用来在Web开发中做远程调试的工作,相当给力。后来也捐给了Apache基金会,并且从ruby移植到了JavaScript,现在可以直接通过npm安装。在最近的广告墙大重构中,这个工具帮了我很大的忙。
安装weinre首先要装node.js,后者在Windows和Mac上直接下包就能装,并且自动配置环境,很方便;在Linux上需要自己编译一次,也不复杂,我之前有篇日志写了,现在还好使,可以看看。
node.js环境搞定后,直接用npm就可以安装weinre了,直接装在全局中最好:
sudo npm install weinre -g
接下来启动weinre,我在这里卡了一阵,大概因为不太了解端口侦听的缘故,我以为直接启动就好,结果从外面连不上(手机连不上连点反应都没有,很难排查),因为侦听的是localhost的ip,也就是127.0.0.1。后来尝试绑定内网ip,才算解决问题:
# 8081是想找一个不常用的端口,后面的ip是我在内网的ip weinre --httpPort 8081 --boundHost 192.168.10.54
这次无论是本机还是手机都可以正常访问了。然后Mac这里可能还会遇到点小问题。虽然我关闭了防火墙,但是Unix自带的ipfw还在工作,会阻止从外面过来的访问(不知道为啥80没问题),所以要给8081端口专门的许可:
sudo ipfw add 8081 allow from any to any
之后就万事大吉了,在HTML里添加 <script src="http://192.168.10.54:8081/target/target-script-min.js#meathill"></script> (域名根据具体情况修改),然后打开 http://192.168.10.54:8081/client/#meathill 就可以查看了,如果有多台终端在调试的话,还可以点选目标机器。
好了,享受下weinre带来的方便快捷的移动端调试吧。(不自己截图了,借用了weinre官网上的图片)
在微博上看到 @HTML5梦工厂 要开一个小型的交流会, @司徒正美 要来分享他的Avalon框架,我算了算,今天正好没事儿,就报名参加了。
自从在宠物派见识到架构给产品开发带来的提升后,我就开始关注各种框架,并且在加入点乐后开始使用Backbone。Backbone是个很好的框架,解决了很多JavaScript的先天不足;当然还有一些地方可以加强,这也是我最近在努力的地方。与Backbone所属的MVC不同,Avalon是一个MVVM框架,与近期开始流行的Angular、Ember.js使用了同一个模式。所以我一直想多了解下这个模式的特点。
不得不说,正美大的普通话和音量有点影响效果。听下来,我的理解是这样:
不过我还是有一些疑问,比如性能、复用性、维护效率之类的。看来有必要用Angular框架做一个项目了(大厂产品应该更有保障些~)。
会上还听了一个CodeJam项目《谁是卧底》的分享,坦白说那哥们对phonegap开发最佳实践的理解还不如我,有机会写篇文章。
最后帮公司打了个广告,希望能收几份简历。回头Nervenet能拿出去见人的时候也去分享下吧。
昨天晚上做了个梦,觉得有点意思,记下来。
有一天,世界性僵尸病毒爆发了,全球一半以上的人口变成了僵尸。(这里的僵尸是最原始的那种,行动缓慢,吃新鲜的肉,击中头部就会死。)在幸存者的共同努力下,这次危机终于被成功控制住了,而且,人们发现,僵尸并不像想象中的那样完全无法控制:由于感染程度的不同,部分僵尸一定程度上保留着生前的技能,只不过因为极度嗜血而无法自控;在药物控制、食物训练之后,甚至可以从事一些高级工作(有点类似僵尸肖恩)。当然,指望他们完全恢复意识暂时是不可能的
后来世界各国通过法案达成协议:
但是,此时世界人口减少一大半,尤其是原本的人口密集地区,劳动力大量短缺,就有人打起了那些“尚能工作”的僵尸的主意。很快,庞大的地下僵尸交易市场就形成了,尤其是生前技能保留的比较完整的,非常值钱。可想而知,即使被感染成了僵尸,音容笑貌,未曾改变,对于活着的人而言,是绝不肯看到自己曾经的亲人沦为货物的;再加上感染的危险存在,所以各国对这种“新奴隶主义”打击也非常重,抓到一定第一时间把僵尸焚毁。原本默许的,大家可以照管亲人变成的僵尸的权力,也被严令禁止了。
就在这个时候,“我”苏醒了。我被病毒改变的只有死灰一样的外表和动静差别极大的代谢系统,静止的时候,与死尸无异;运动的时候,代谢系统又会快速将能量供给到全身。(就像灵光波动拳那样,发动的时候会重现年轻。)我这次苏醒是因为一个什么原因(忘了),当我发现社会秩序已经基本恢复后,就像赶紧回到家人的身边。
于是,我需要在僵尸贩子、各国警察的层层抓捕下,东躲西藏;又需要对抗自己对血肉的渴望,对昏睡的渴望;还需要保持运动量,避免以僵尸的面目示人。在刚刚脱离危机自顾不暇个个自危的人中,寻找真正可以依赖可以托付的伙伴,让他们帮我回到家乡。