Pull to refresh

Притча о шаблонах

JavaScript
— Здравствуй *с широко развевающейся по лицу улыбкой* дружок.
— Ваа! *с ярким блеском в широко распахнутых глазах* Тётя Ася приехала!
— Да, и у меня есть для тебя новая сказка *присела и взяла малыша за руки* хочешь послушать?
— Конечно! *слегка смутился и отвёл взгляд* Мне тут дядя такие страшные истории рассказывал…
— Ну, надеюсь моя история тебя не испугает *потрепала его по волосам* Она должна научить тебя мыслить шаблонно.
— Эээ? *лицо перекосилось от недопонимания* Это как?
— М… сейчас узнаешь *подмигнула и взяла на ручки* Вот когда тебе нужно вставить переменные в строку — ты как поступишь?
— Ну… *взял карандаш и чирканул на лежащей рядом бумажке* примерно так:
var query= 'xxx'
var resultCount= 512
var message= 'По запросу <kbd>' + query + '</kbd> найдено страниц: ' + resultCount

— Ты ничего не забыл? *победоносно подняла голову*
— Да вроде нет… *уткнулся носом в код, ещё раз внимательно его проверяя*
— Что, если пользователь введёт… *выдержала многозначительную паузу и добавила*
<iframe src="javascript:alert('ahtung')"></iframe>

— А, ну да *виновато приписал экранирование*
var message= 'По запросу <kbd>' + query.escapeHTML() + '</kbd> найдено страниц: ' + resultCount

— Молодец… *разочарованно выдохнула* Только вот боюсь — это не последний раз, когда ты забыл обезвредить данные.., а как будешь прикручивать к этому коду языковую локализацию?
— Ну… *напряжённая работа мысли скукожила некогда миленький лобик* Вынесу в отдельный массив:
var texts= [ 'По запросу <kbd>', '</kbd> найдено страниц: ' ]
var message= texts[0] + query.escapeHTML() + texts[1] + resultCount

— И не влом же тебе будет писать каждый раз эти texts[n], чередуя их с данными? *взяла карандаш и, зачеркнув его писанину, нарисовала свою* Смотри, так использовать гораздо проще:
var template= TT.template( 'По запросу <kbd>{0}</kbd> найдено страниц: {1}' )
var message= template([ query.escapeHTML(), resultCount ])

— Да *пытается переварить* Наверно…
— Но когда параметров больше одного — лучше давать им говорящие имена *ещё несколько движений карандашом* для наглядности:
var template= TT.template( 'По запросу <kbd>{query}</kbd> найдено страниц: {count}' )
var message= template({ query: query.escapeHTML(), count: resultCount })

— Ха! *радостно вскочил* Получилось даже длинее чем у меня!
— Не проблема! *уверенно позачёркивала лишнее* Воспользуемся автоэкранированием:
var template= TT.html( 'По запросу <kbd>{query}</kbd> найдено страниц: {count}' )
var message= template({ query: query, count: resultCount })

— То есть теперь… *призадумался над последним кодом* Мне даже не придётся заботиться об экранировании данных?!7
— Точно! *коснулась пальцем кончика его носа* Все данные будут проэкранированы, кроме тех, для которых ты явным образом не скажешь отключить фильтрацию:
var message= template({ query: TT.value( highLightedQuery ), count: resultCount })

— А что за функция такая *широким жестом тыкнул пальцем в лист* TT.value?
— Она просто создаёт функцию *слегка запнулась, решив проиллюстрировать свои слова* которая всегда возвращает определённое значение:
TT.value= function( data ){
return function( ){
return data
}
}

— То есть, если вместо данных передать функцию, возвращающую данные, то шаблонизатор будет считать, что данные возвращаемые функцией безопасны для вставки в шаблон? *сам офигел, что сказал такую сложную фразу* Да?
— Именно! *раскинула руками* А вот, смотри, ещё интересная функция:
TT.pipe= function( list ){
if( !list ) list= []
var len= list.length
return function( data ){
for( var i= 0; i < len; ++i ) data= list[ i ]( data )
return data
}
}

— А она что делает? *внимательно всматривается в волшебный метод*
— Она берёт набор фильтров и выстраивает их в одну цепочку *поняла, что без примера тут не обойтись* Вот, смотри, следующие две строчки эквивалентны:
var text= decodeURIComponent( encodeURIComponent( text ) )
var text= TT.pipe([ encodeURIComponent, decodeURIComponent ])( text )

— Брр *встряхнул головой* мистика какая-то!
— Это ещё что! *усмехнулась* Сам шаблонизатор ещё круче:
TT.template= new function( ){
var searcher= /((?:[^\{\}]|\{\{|\}\})*)(?:\{|$)|([^\{\}]*)(?:\})/g
return function( str, filter ){
if( !filter ) filter= TT.pipe()
var parts= []
String( str ).replace
( searcher
, function( str, val, sel ){
if( sel !== void 0 ){
parts.push( function( data ){
data= data[ sel ]
switch( typeof data ){
case 'undefined': return '{' + sel + '}'
case 'function': return data()
default: return filter( data )
}
})
} else if( val ){
val= val.split( '{{' ).join( '{' ).split( '}}' ).join( '}' )
parts.push( TT.value( val ) )
}
}
)
return TT.concater( parts )
}
}

— Ыыыы *чуть ли не плача* Вы же обещали не пугать!
— Да ладно тебе! *обняла в охапку* Зато какие гламурные на его основе получаются плагинчики:
TT.uri= function( tpl ){
return TT.template( tpl, TT.uri.encoder() )
}
TT.uri.encoder= TT.value( encodeURIComponent )
TT.uri.decoder= TT.value( decodeURIComponent )

— Что это? *сопит, уткнувшись носом в грудь*
— Это шаблонизатор для URI *дала ему карандаш в руку* Пиши:
var searchURI= TT.uri( '/search/?q={query}&count={count}' )
location.href= searchURI({ query: '&&&', count: 10 })

— Эм… *повеселел даже* А как быть с html?
— Да так же самое:
TT.html= function( tpl ){
return TT.template( tpl, TT.html.encoder() )
}
TT.html.encoder= new function( ){
var parent= document.createElement('div')
var child= parent.appendChild( document.createTextNode( '' ) )
return TT.value( function( data ){
child.nodeValue= data
return parent.innerHTML.split( '"' ).join( '&quot;' ).split( "'" ).join( '&apos;' )
})
}
TT.html.decoder= new function( ){
var parent= document.createElement('div')
return TT.value( function( data ){
parent.innerHTML= data
return parent.firstChild.nodeValue
})
}

— Крутбл!
— Но и это ещё не всё! *бешенно жестикулируя* Для html можно сделать ещё и так, чтобы в результате выдавался HTMLNode с соответствующим содержимым:
TT.dom= function( tpl ){
return TT.pipe([ TT.html( tpl ), TT.dom.parser() ])
}
TT.dom.parser= new function(){
var parent= document.createElement( 'div' )
return TT.value( function( html ){
parent.innerHTML= html
var childs= parent.childNodes
if( childs.length === 1 ) return childs[0]
var fragment= document.createDocumentFragment()
while( childs[0] ) fragment.appendChild( childs[0] )
return fragment
})
}
TT.dom.serializer= new function(){
var parent= document.createElement( 'div' )
var child= parent.appendChild( document.createTextNode( '' ) )
return TT.value( function( node ){
parent.replaceChild( node.cloneNode( true ), parent.firstChild )
return parent.innerHTML
})
}

— И как это использовать? *в глазках загорелись искорки халявы*
— Да очень просто *растеклась мыслью по листу бумаги*
var link= TT.dom( '<a href="{uri}">{title}</a>' )({ uri: '/', title: 'на старт' }) // HTMLAnchorElement
var userName= TT.dom( '<b>{head}</b>{tail}' )({ head: 'T', tail: 'enshi' }) // HTMLFragment

— Гм… *потихоньку стаскивая исходники* Действительно не сложно…

______________________
Текст подготовлен в Редакторе Блогов от © SoftCoder.ru
Tags:javascripttemplatefunctional programming
Hubs: JavaScript
Total votes 232: ↑157 and ↓75 +82
Views1.7K

Popular right now