Pull to refresh

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

Reading time8 min
Views1.8K
 — Здравствуй *с широко развевающейся по лицу улыбкой* дружок.
 — Ваа! *с ярким блеском в широко распахнутых глазах* Тётя Ася приехала!
 — Да, и у меня есть для тебя новая сказка *присела и взяла малыша за руки* хочешь послушать?
 — Конечно! *слегка смутился и отвёл взгляд* Мне тут дядя такие страшные истории рассказывал…
 — Ну, надеюсь моя история тебя не испугает *потрепала его по волосам* Она должна научить тебя мыслить шаблонно.
 — Эээ? *лицо перекосилось от недопонимания* Это как?
 — М… сейчас узнаешь *подмигнула и взяла на ручки* Вот когда тебе нужно вставить переменные в строку — ты как поступишь?
 — Ну… *взял карандаш и чирканул на лежащей рядом бумажке* примерно так:
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:
Hubs:
+82
Comments164

Articles