为了给小泥鳅实现一套好的模板机制,曾经花了大量时间来分析目前互联网上的成熟,流行的通用程序,包括blog,cms,shop,bbs/.net,php,asp等,总结下来主要有以下几种:
1、CSS
通过加载不同的CSS文件来实现不同的展现效果。
相信熟悉CSS的话,便知CSS的强大,asp.net的默认主题基于此实现
优点:维护方便,速度快
缺点:2/8原则,可以实现大部分功能,灵活性不够
2、模板引擎
通过加载不同的模板文件(模板文件内容可不同),使用模板引擎自己的语言机制动态替换数据,来实现不同的展现效果。
优点:灵活,可实现绝大部分需求
缺点:影响性能,需要引入第三方模板引擎(当然可以自己开发),增加复杂度。
3、.ascx用户控件,母版页(.Net平台独有)
通过加载不同的.ascx用户控件来实现不同的展现效果。当然也可以动态加载. master母版页。
原理和使用第三方模板引擎类似。
优点:可使用.net语言,比模板引擎更灵活,可实现任何需求
缺点:通过反射机制影响性能(据说,未验证),.net特征偏多,对美工不友好
4、生成.aspx文件
需要使用一次模板引擎生成.aspx文件,利用URL重写加载不同的.aspx文件,运用时与模板引擎和模板文件无关。
优点:灵活,速度快
缺点:维护相对麻烦,修改模板后需要重新生成.aspx文件, 加入第三方模板引擎,增加复杂度。
5,xml+xslt
名词解释:XSL 指扩展样式表语言(EXtensible Stylesheet Language),XSLT 指XSL 转换
设计思想跟模板引擎一样,不过xslt是通用标准
优点:标准,跨平台,跨语言
缺点:不够灵活,容易出错, 对美工来说,偏复杂
问题来了,哪种更合适小泥鳅的呢?受Community Server,BlogEngine.NET等国外程序的影响,老农开始选择了第3种,通过动态加载不同用户控件的方式来实现模板机制,功能很给力,并且在相当长的内部开发阶段使用了这种方式(胎死腹中),同时也是在这个阶段,在与其它职业的同事,朋友交流中,慢慢觉得这可能不是一种最合适的方式(实践出真知),对于广大普通用户,特别是主题设计者,太多的.net特性会让他们看花了眼。
小泥鳅的定位一个成熟,通用,面向所有人群的博客程序,最后把焦点放在了模板引擎上,
现在.net平台的主要模板引擎有Nvelocity, StringTemplate等,经过多方面的测试,评估,最终选择了Nvelocity,当然不排除今后有能力,有时间自己造轮子。
Nvelocity源自Java,历史悠久,语法优雅,减少了重复学习的成本
如以下代码用于输出最新日志(简单就是美):
<div id="recentposts" class="widget">
<h3>最新日志</h3>
<ul>
#foreach($item in ${recentposts})
<li>${item.link}</li>
#end
</ul>
</div>
根据发布以来的反馈统计,效果还不错,大家也来做皮吧 ^_^
附:
源码下载:http://files.cnblogs.com/wei/Loachs_1.2_src.rar
官方网站:http://www.loachs.com/
QQ群:12018034
引子
这几天一直在做DNT的二次开发,产生 了一些经验,跟大家分享下。
客户需要导入旧论坛数据和开发一个新活动功能。
所以详细研究DNT的帖子相关表(dnt_topics,dnt_postid, dnt_posts1)。
向园子里的代兄和戏水请教,代兄反应极快,他说戏水是做数据转换的,比他更有经验!汗,并要我去官方论坛发贴。
我去发了,点击量很高,至今0回复。
而给戏水的消息,有去无回。
看来,得自已动手。花了些时间,终于明白:
dnt_topics 保存主题相关信息,注意没保存正文。主键为自动增长。
dnt_posts1 系统保存主题正文,和回帖。主键却来源于dnt_postid.
这样做可以提高查询主题列表的速度。
但如果站长需要从主题正文提取部分做为摘要显示,那就得不偿失了。
用dnt_postid 单独生成帖子主键,不太理解,反正是增加了导入其它论坛数据的难度。
或者增加了将数据导出到其它论坛的难度。
反正导入旧数据,花了好长时间,才弄明白。
人为制造门槛?
数据导入完成。
开发活动功能,还是比较顺利和快速的。基本可以借用DNT的通用类和方法。
经验
- 导入数据后,dnt_topics 和dnt_posts1 的 lastposterid,posterid 必须大于0,不然主题和回帖不能翻页或不显示。花了好久时间还找此原因。
- 需要手工改变dnt_postid的pid的初始值,不然回复贴会出现在前面,pid也会重复。
BUG
- 如果修改表情组名称和表情文件夹名称不同,发贴时将不能转换表情。修改默认表情名将出现图片出现红叉叉.
- 在默认安装下没有相册和空间,查找查看用户的所有发贴时。如/search.aspx?posterid=2988,出现"对象名 'dnt_albums' 无效。对象名 'dnt_spaceposts' 无效。"
- 修改广告时没有绑定原状态,展现方式每次都变成默认。
- 有时登陆时提示你已登陆,不用重复登陆,返回首页看不到登陆信息,查看本机COOKIE却又COOKIE记录。
- 用户分组的问题。现在分为积分组,管理组,系统组,特殊组。不明白特殊组存在的意义。而且还有问题。添加一个特殊组,修改时关联管理组,就变成了管理组,再修改时取消关联管理组,就变成了积分组。变不回特殊组了。
- 无缘无故出现错误,刷新一下又好了。如: 无法在“Discuz.Plugin.Spread”已存在的情况下创建/影像复制该文件。
- ...
不足(个人看法)
- 添加分表后,不能撤销,或删除,估计有些站长喜欢抱着试试看的心态。
- 太庞大,刚安装,就占用了二三十MB的空间了.asp.net 较asp 占资源,特别是内存,不太适合虚拟主机,VPS也比较悬。
- 还要N多操作上的不便。 比例添加表情要将相应文件夹拷到相应目录,然后刷新页面。但我在后台页面上始终找不到相应的帮助,或者说明。
- 上传头像时路径前加了"/",而选择系统头像时没"/";完全可以统一的。给二次开发者造成不变.
- 表太多了,越来越不像论坛了。
- ...
总结
- 套用一名话,革命尚未成功,DNT仍需努力。
- ...
HTML在线编辑器相信大家见得多了,有些流行的在线编辑器具有很丰富的功能。但美中不足的是,现有的HTML在线编辑器设置字号大小通常只限于1-7号字而已,不能更好地满足人们的需要。能不能实现像Word那样任意地设置字号大小呢?经过长时间的攻关,Bound0终于可以肯定地回答这个问题了,呵呵呵!请大家共同分享此“Eureka之喜悦”吧!
我们通常所见的内嵌在网页中的HTML在线编辑器,其核心本质是一个IFRAME
例如:
<IFRAME id="Editor" Name="Editor" style="WIDTH: 550; HEIGHT: 480"></IFRAME>
令 Editor.document.designMode="ON";
此时这个IFRAME就是一个设计模式的HTML编辑器了,可以通过脚本
Editor.document.selection.createRange().execCommand(command)
对选中的内容执行command,进行编辑操作。
由于浏览器本身提供的编辑功能非常有限,因此现在流行的HTML在线编辑器大都进行了一些扩展。
在本文发表之前,HTML在线编辑器在设计模式下的字号(字体大小)任意设定功能尚无成功实现,本文所述技术不仅在此方面实现了零的突破,还对HTML在线编辑器的其他扩展功能的实现作出了广泛的启示。
现在流行的HTML在线编辑器的功能可以说已经很丰富了,为什么唯独任意设置字体大小这个看起来并不华丽的功能没有被做出来呢?让我们先来了解一下其中的原因好了:
假设编辑区的HTML内容(Editor.document.body.innerHTML)是:
<H1>逍遥主义者的宣言</H1>
<H3>十个小老头
<BR>朱氵太子
</H3>
<P>我们不是败家浪子,我们从不游手好闲;我们不是无用书生,我们从不怨天忧地;我们不是嬉皮士,我们从不玩世不恭;我们不是妄想家,我们从不脱离实际;我们不是阿Q,我们从不麻木颓废;我们不是狂人,我们从不争风吃醋。我们是创造者,我们是改变者,我们是革命者。我们不是在不幸中去幻想幸福的人,我们甚至不是去追求、寻找幸福的人,我们是自己制造幸福的人,我们是快乐的发源者。首先,在我们经历必当经历的经历时,我们不允许自己不快乐!这就是,逍遥主义的真谛。</P>
<P><BIG>将你的梦打开,填进我的希望。谁都不必介怀,一点点的分量。面对别人述说,命运烛转轮回,世间天空海阔,种种因缘寂晦……</BIG></P>
假设选中的是红色的部分,则Editor.document.selection.createRange().htmlText的内容是:
<P>者。我们不是在不幸中去幻想幸福的人,我们甚至不是去追求、寻找幸福的人,我们是自己制造幸福的人,我们是快乐的发源者。首先,在我们经历必当经历的经历时,我们不允许自己不快乐!这就是,逍遥主义的真谛。</P>
<P><BIG>将你的梦打开,填进我</BIG></P>
看见多出来几个蓝色的标签了吧?产生这种现象是因为:“任何时候,document.selection.createRange().htmlText取出来的html代码都是完整成对的,即使当前选中的内容横跨了几个不同的标签”(卖坏梨语),所以IE会自作主张地把不完整的标签补齐。
就是因为这个原因,当选中的内容在Editor.document.body.innerHTML中对应的代码包含不完整的标签时,在Editor.document.selection.createRange()上做pasteHTML可能会吃不进去(有不完整的<table>等时),或者出现明显的BUG。
而现在的HTML在线编辑器所提供的除了基本的Editor.document.selection.createRange().execCommand(command)之外的各种功能几乎都是通过在Editor.document.selection.createRange()上做pasteHTML来实现的。所以字号的自由设定才始终没能实现。
一切关于“问题的解决之道”的方法论都不外乎是“变通”二字的某种讲法。既然这个功能不适宜用Editor.document.selection.createRange()的pasteHTML()方法来实现,我们就不用它好了。一个变通的想法是:通过改变Editor.document.body.innerHTML的全局来实现。
很显然,至少在理论上,整体改变Editor.document.body.innerHTML可以实现任何我们想要的HTML编辑效果。
多好,随着灵光一闪,战略上的大问题在一秒钟之内就被我们搞定了!接下来让我们看看具体的实现方法吧:
要想通过改变Editor.document.body.innerHTML来给选中的内容设置字号大小,首先要解决的一个问题是在Editor.document.body.innerHTML中对选中内容所对应的代码进行定位。这算不上是个超级复杂的问题,但我知道对这个问题的解答也是很多人梦寐以求的。或许能有很多种解,下面给出我Bound0的办法。
显然这个问题不可以像一些人想象的那样随随便便地用正则或者查找之类的方法就搞定,设想我在
Bound0000000000000000000000000000000000000000000000000000中随便选中了一个0(表示为红色),用正则随便查到了一个0,很难确保就是我选中的那个,同样的道理,如果是在雷同的若干段HTML代码中选中了一段,用查找所选字符的方法是不能确保正确定位的。
这个问题的合理的解应该能把Editor.document.body.innerHTML分成三段:partA—选中内容之前的内容所对应的代码、partB—选中内容所对应的代码(就是前面例子中红色的部分)、partC—选中内容之后的内容所对应的代码。(在全选的时候,partA和partC都是空字符串;选中内容为空的时候,从开头到光标位置的内容所对应的代码是partA,partB为空字符串,光标之后的内容所对应的代码是partC)
看看主要的代码:
CODE:
function first()
{
//首先要取得编辑区的内容
var oSel = Editor.document.selection.createRange();
var conts=''+oSel.htmlText //内容选中部分对应的代码,首尾可能带有多余标签(就是前面例子中的蓝色标签)。
var textLength = Editor.document.body.innerText.length
oSel.moveStart("character", -1*textLength) //把选择区的开始位置往前闪,再取一次内容
var contp=''+oSel.htmlText //选中部分及选中部分前的内容,末尾可能带有多余标签。
var conta=''+Editor.document.body.innerHTML //整个内容
var contpa=''
var partC=""
var partB=""
var partA=""
//接下来通过两组循坏,用上面取得的三个内容互相“磨”,把多余的标签“磨”掉。
var m=0
m=conta.indexOf(contp.substr(0,3)) //校正对齐contp和conta的开始位置,有时候conta开始处可能会有多余的<p>,造成两者对不齐
for(var f=contp.length;f>0;f--)
{if(conta.substr(m,f)==contp.substr(0, f)){contpa=contp.substr(0,f);partC=conta.substr(m+f);break}}
var k=contpa.length
for(var u=conts.length;u>0;u--)
{if(conts.lastIndexOf(contpa.substr(k-u))!=-1){partB=contpa.substr(k-u);partA=contpa.substr(0,k-u);break}}
if(conts.length==0)partA=contpa
//显示按要求分好的A、B、C三段内容。
alert(partA)
alert(partB)
alert(partC)
}
实际使用的代码比这个要复杂一些。因为想要应付各种特殊情况、考虑周全也不容易呢。
上面代码所取到的conts和contp常常会包含浏览器自动添加的一些\r\n(回车、换行符),这会造成后面“磨”的困难,有必要先进行格式化。但是由于对于pre、textarea、script、style和xmp这几种标签的内容来说\r\n可能是有意义的,所以不能简单地用.replace(/[\r\n]/g,"")的办法去除。必须既要去除浏览器自动添加的\r\n,又要保全pre、textarea、script和xmp这几种标签的内容。这种局面看起来确实复杂,不过好在浏览器不会在我们要保全的那几种标签的内容里自动添加\r\n,而这个时候原本是罪魁祸首的document.selection.createRange().htmlText的标签自动封闭机制反倒为我们提供了方便:除非选中的内容刚好处于一个标签的内部,否则在conts中将出现完整成对的标签,这样我们就可以比较容易地把位于pre、textarea、script、style和xmp这几种标签中的内容区分出来,只对其他内容进行去除\r\n的操作。而对于选中的内容刚好处于一个标签的内部的这种情况,它的具体情况可能又是五花八门的,我个人采取的办法是把格式化和不格式化都尝试一下,除非格式化的结果令partB长度较长(这说明浏览器自动添加的那些\r\n使“磨”出来的partB长度变短),否则就不格式化。
有时候浏览器自动补全的标签并非添加在选中区域的最外围。例如有时会把</p>结束标签添在</font>标签之前,而这里的</font>标签应该是保留在partB中的,如果把</font>连同</p>一起“磨”掉就不对了。对此采取的办法是检查被“磨”掉的碎渣部分,把碎渣捡起来“磨”好,再装到partB的末尾。
运行演示例:
运行代码框
[Ctrl+A 全部选择 提示:你可先修改部分代码]
Tip: 留心的朋友可能已经想到了:这段代码还可以用于实现在 “设计/代码” 模式切换过程中,令选中的文字或光标位置保持对应。
成功地实现对内容选中部分的定位之后,接下来要解决的第二个问题是:根据需要改变选中部分的代码。
HTML代码是由标签组成的,我们需要处理的是对字号大小有影响的标签,可分为6种情况:
1、font标签,毋庸置疑,这个标签需要重点处理;
2、h[1-6]、pre、button、listing、big、small、tt、code、kbd、samp等具有字号改变作用的标签
3、浏览器所能识别的大多数标签在带有style="FONT-SIZE: xxx;"样式属性时将可能对字号大小产生影响;
4、select、input、option和object,这四个标签也可以带上FONT-SIZE样式(算是稀奇古怪的用法),但其效果特殊,应将它们从上一条中排除;
5、sup和sub虽然也会影响字号大小,但是它们的这种影响在重新设置字号时应该被保留,所以对这两个标签无需作处理。
6、其他会造成位于自己前面的font标签的字号效力中断的标签,例如<td>、</td>、<caption>、</caption>、<th>、</th>、<tr>、</tr>、<table>、</table>、<thead>、</thead>、<tbody>、</tbody>、<tfoot>、</tfoot>……还可能有哪些一时也想不起来,慢慢完善吧。
处理过程如下(具体代码见演示例):
- 0、在做所有的处理之前,先要将textarea、xmp、script和style标签的内容封印起来,以保护它们不被破坏。
- 1、处理font标签:
(1)尝试将PartB内的所有font标签配对。(Tip:这段代码也可以用来做其他的“语法分析”工作)
(2)将所有在PartB内封闭的标签(能配上对的)中的字号属性去除。
(3)将partB中未结束的<font>标签置标(做上标记)。
(4)将partB中没有配上对的</font>结束标签(开始标签在partA中)置标。
- 2、处理其他标签:将partB中如前所述的会造成位于自己前面的font标签的字号效力中断的各种HTML标签置标。
- 3、收尾工作:(怎么?已经结束了吗?)
(1)将上面两步产生的所有置标位置视为“中断点”。
(2)在partB开头和第一个“中断点”之间所夹的内容、最后一个“中断点”和partB末尾之间所夹的内容,以及各个“中断点”之间所夹的内容的前面添上一个行使字号效力的<font ……>,内容的后面添上</font>。
(3)解除textarea、xmp、script和style标签内容的封印。
(4)把partA+partB+partC拼接起来,输出到Editor.document.body.innerHTML。
OK. 一切都搞定了!
完整的运行演示例:
运行代码框
[Ctrl+A 全部选择 提示:你可先修改部分代码]