2015年第7周技术小结
建立适当的模型生成 xml 文档
客户发货时,需要我们为每一个订单都提供一份 invoice 数据,格式是 xml, 这份 invoice 数据有点复杂,为了方便描述,将其 简化,下面是简化后的 invoice xml 文档,
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<Invoice>
<InvoiceNumber>IDE2014000000001</InvoiceNumber>
<InvoiceTypeCode>SATIS</InvoiceTypeCode>
<ScenarioType>TICARIFATURA</ScenarioType>
<DocumentCurrencyCode>TRL</DocumentCurrencyCode>
<IssueDate>2013-08-10</IssueDate>
<PayableAmount>216.16</PayableAmount>
<TaxExclusiveAmount>200</TaxExclusiveAmount>
<TaxInclusiveAmount>235.16</TaxInclusiveAmount>
<DiscountRate></DiscountRate>
<DiscountTotal></DiscountTotal>
<OrderNumber></OrderNumber>
<OrderDate></OrderDate>
</Invoice>
在我们的数据库中并没有 invoices 这么一个表,客户需要的 invoice 数据来自于我们现有的多个模型,比如 Order, Product, Variant 等, 很自然的,我们可以为 invoices 建立一个模型: Invoice,
class Invoice
attr_reader :number
attr_reader :type_code
attr_reader :scenario_type
attr_reader :document_currency_code
attr_reader :issue_date
attr_reader :payable_amount
attr_reader :tax_exclusive_amount
attr_reader :tax_inclusive_amount
attr_reader :discount_rate
attr_reader :discount_total
attr_reader :order_number
attr_reader :order_date
end
这样 invoices xml 文档里的数据就都可以由 Invoice 模型来提供了, 比如,
number => InvoiceNumber
type_code => InvoiceTypeCode
我们可以为 number => InvoiceNumber
这类的映射建立一套 DSL,
class Invoice
xml_attr :number, :type_code, prefix: 'invoice', camelize: true
end
这套 DSL 的作用就是把模型 Invoice 的属性转换为 xml 文档内的 columns。
我把这个 DSL 的实现写成了一个 module,
module DataSource
module Xmlable
def self.included base
class << base
attr_reader :xml_col_attr_pairs
end
base.extend ClassMethods
base.send :include, InstanceMethods
end
module ClassMethods
def xml_attr *attrs, opts
@xml_col_attr_pairs ||= []
cols = attrs.dup
if opts[:prefix].present?
cols = attrs.map {|attr| [opts[:prefix], attr].join('_') }
end
if opts[:camelize] == true
cols = cols.map(&:to_s).map &:camelize
end
cols.each_with_index {|col, index|
@xml_col_attr_pairs << [col, attrs[index]]
}
end
end
module InstanceMethods
def col_attr_pairs_to_xml
build_xml do |xml|
xml_col_attr_pairs.each do |pair|
col_value = send pair[1]
xml.send pair[0], col_value
end
end
end
def xml_col_attr_pairs
self.class.xml_col_attr_pairs
end
def build_xml
xml = ::Builder::XmlMarkup.new
yield xml
xml.target!
end
end
end
end
Invoice 的完整版本是,
class Invoice
include DataSource::Xmlable
attr_reader :number
attr_reader :type_code
attr_reader :scenario_type
attr_reader :document_currency_code
attr_reader :issue_date
attr_reader :payable_amount
attr_reader :tax_exclusive_amount
attr_reader :tax_inclusive_amount
attr_reader :discount_rate
attr_reader :discount_total
attr_reader :order_number
attr_reader :order_date
xml_attr :number, :type_code, prefix: 'invoice', camelize: true
xml_attr :scenario_type, :document_currency_code, :issue_date, camelize: true
xml_attr :payable_amount, :tax_exclusive_amount, camelize: true
xml_attr :discount_rate, :discount_total, :order_number, :order_date, camelize: true
def initialize attrs
attrs.each {|attr, value|
if respond_to? attr
instance_variable_set "@#{attr}", value
end
}
end
def to_xml
build_xml do |xml|
xml.instruct! :xml, version: '1.0', encoding: 'UTF-8', standalone: 'yes'
xml << xml_without_instruct
end
end
def xml_without_instruct
build_xml do |xml|
xml.Invoice do
xml << col_attr_pairs_to_xml
end
end
end
end
调试:
invoice = Invoice.new(number: 'A000119', type_code: 'ISE')
invoice.to_xml
输出:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Invoice>
<InvoiceNumber>A000119</InvoiceNumber>
<InvoiceTypeCode>ISE</InvoiceTypeCode>
<ScenarioType></ScenarioType>
<DocumentCurrencyCode></DocumentCurrencyCode>
<IssueDate></IssueDate>
<PayableAmount></PayableAmount>
<TaxExclusiveAmount></TaxExclusiveAmount>
<DiscountRate></DiscountRate>
<DiscountTotal></DiscountTotal>
<OrderNumber></OrderNumber>
<OrderDate></OrderDate>
</Invoice>
JavaScript 编程实践 Site.View
以前把很多的 javascript 代码写在了 html 页面里了,这样造成的一个问题是时间一久,大家都找不到自己写的 javascript 代码在哪了或者 找起来非常费时费力, 还有一个问题就是你无法把写在 html 页面里的 javascript 代码压缩合并,所以我决定尽量把所有的 javascript 代码 都写在单独的 js 文件中。为此我实现了一个叫 Site.View 的类, 当然在 javascript 中, Site.View 其实是一个普通的 function。
Site.View 能帮我做三件事情:
- 可以将所有 javascript 代码放到一个文件中,而不会产生命名冲突等;
- 只有当页面出现相应的 DOM 元素时才会执行对应的代码;
- 提供适当的扩展;
Site.View 的实现:
var Site = Site || {};
Site.View = function(){
var template_mapper = {};
var View = function(template_name, args){
var findContainer = function(name){
var container = document.querySelector('[view-template="' + name + '"' + ']');
return container;
};
var registerEventHandler = function(target, type, callback) {
if(target.addEventListener){
target.addEventListener(type, callback);
} else {
target.attachEvent('on' + type, callback);
}
};
this.template_name = template_name;
this.container = findContainer(template_name);
this.param = function(object){
var encodedString = '';
for (var prop in object) {
if (object.hasOwnProperty(prop)) {
if (encodedString.length > 0) {
encodedString += '&';
}
encodedString += encodeURI(prop + '=' + object[prop]);
}
}
return encodedString;
};
this.ajax = function(ele){
var xhr = new XMLHttpRequest();
var ext_obj = {};
ext_obj.loadRemoteHtmlFragment = function(callback){
var url = ele.getAttribute('data-uri');
xhr.open('GET', url);
xhr.setRequestHeader('Content-Type', 'text/html');
xhr.onload = function(){
if(xhr.status === 200){
ele.innerHTML = xhr.responseText;
if(callback){
callback();
}
}
};
xhr.send();
}
return ext_obj;
};
this.data = (function(){
var lastId = 0;
var store = {};
return function(ele){
return {
set: function(info) {
var id;
if (ele.myCustomDataTag === undefined) {
id = lastId++;
ele.myCustomDataTag = id;
}
store[id] = info;
},
get: function() {
return store[ele.myCustomDataTag];
}
}
};
}());
this._ = function(ele){
var ext_obj = {};
ext_obj.on = function(type, callback){
if(ele[0]){
for(var i=0; i < ele.length; i++){
registerEventHandler(ele[i], type, callback);
}
} else {
registerEventHandler(ele, type, callback);
}
};
ext_obj.removeClass = function(class_name){
var reg_str = '\(\^\|\\s\)' + class_name + '\(\\s\|\$\)';
var reg = new RegExp(reg_str, 'g');
ele.className = ele.className.replace(reg, '');
};
return ext_obj;
};
this.parseCssTextToObject = function(css_text){
var pairs = css_text.split(';');
var obj = {};
for(var i = 0; i < pairs.length; i++){
if(pairs[i]){
var kv = pairs[i].split(':');
var key, value;
if(kv[0]){
key = kv[0].replace(/(^\s+)|(\s+$)/, '');
}
if(kv[1]){
value = kv[1].replace(/(^\s+)|(\s+$)/, '');
}
if(key){
obj[key] = value
}
}
}
return obj;
};
this.t = function(key){
return Csite.i18n.t(key);
};
var template = new template_mapper[template_name](this, args);
return template;
};
View.loadTemplate = function(template_name, context){
template_mapper[template_name] = context;
};
View.render = function(template_name, args){
document.addEventListener('DOMContentLoaded', function(){
var container = document.querySelector('[view-template="' + template_name + '"' + ']');
if(container){
var template = new View(template_name, args);
if(template.render){
template.render();
}
}
})
}
return View;
}();
那么怎么使用 Site.View? 假设我们有下面的 html 页面, 里面定义了一个 view-template="home.index.alert"
, 我们需要在
home.index.alert 加载完后执行 alert('ok')
,
<html>
<head>
<script src='site_view.js'></script>
</head>
<body>
<div view-template="home.index.alert">
</div>
</body>
</html>
Site.View.loadTemplate('home.index.alert', function(view){
alert('ok');
})
Site.View.render('home.index.alert');
Site.View 使用 view-template 去查找相关的 DOM, 如果我们在页面上看到了 view-template="home.index.alert"
,
那么很容易通过 “home.index.alert” 找到与其相关的 javascript 代码。