UNPKG

13.4 kBtext/coffeescriptView Raw
1fs = require('fs')
2coffee = require('coffee-script')
3
4K = require('kcore')
5
6COMPACT = true
7# hard LB - always present
8LBH = '\n'
9# soft LB - present only when COMPACT = false
10LB = if COMPACT then '' else '\n'
11
12# turn off the cache when you are rapidly developing js/css etc.
13RESOURCE_CACHE_ON = true
14RESOURCE_CACHE = {}
15RESOURCE_COMPILED_CACHE = {}
16
17class K.JsCollector
18 constructor: () ->
19 @js = ''
20
21 _generateJsTagFromText: (jsTxt) ->
22 return '<script language="javascript" type="text/javascript"><!--' + LBH + jsTxt + LBH + '--></script>' + LB
23
24 getAllAsTag: () ->
25 rv = ''
26 if @js != ''
27 rv = @_generateJsTagFromText(@js)
28 return rv
29
30 getAllAsOnLoadCode: () ->
31 rv = ''
32 if @js != ''
33 onLoadJs = '$(function() {' + LB + @js + LB + '});'
34 rv = @_generateJsTagFromText(onLoadJs)
35 return rv
36
37 pushFromTxt: (txt) ->
38 @js += txt.trim()
39
40class K.NateHtml
41 constructor: (parent) ->
42 @children = []
43
44 delete: () ->
45 ; # TODO: implement this, but at the moment we need it only in K.NateHtmlDom
46
47 newNate: (elem = null) ->
48 return new K.NateHtml(elem)
49
50 push: (child) ->
51 @children.push child
52
53 render: () ->
54 txt = ''
55 for child in @children
56 txt += child.render()
57 return txt
58
59 @setResourceCacheEnabled: (onOff) ->
60 RESOURCE_CACHE_ON = onOff
61
62class K.NateHtmlHead extends K.NateHtml
63 constructor: (parent) ->
64 super(parent)
65 @htmlDocument = parent
66 @css = ''
67 @meta = {}
68 @alternateLinksForLanguages = null
69
70 setTitle: (title) ->
71 @meta.title = title
72
73 setMetaAuthor: (author) ->
74 @meta.author = author
75
76 setMetaDescription: (description) ->
77 @meta.description = description
78
79 setMetaKeywords: (keywords) ->
80 @meta.keywords = keywords
81
82 setLinksForAlternateLangauges: (links) ->
83 @alternateLinksForLanguages = links
84
85 render: () ->
86 txt = '<head>' + LB
87
88 # title and metas
89 txt += '<meta charset="UTF-8">' + LB
90 if @meta.title?
91 txt += '<title>' + @meta.title + '</title>' + LB
92 if @meta.description?
93 txt += '<meta name="description" content="' + @meta.description + '">' + LB
94 if @meta.keywords?
95 txt += '<meta name="keywords" content="' + @meta.keywords + '">' + LB
96 if @meta.author?
97 txt += '<meta name="author" content="' + @meta.author + '">' + LB
98 txt += '<meta http-equiv="Cache-Control" content="no-cache"/>' + LB
99 txt += '<meta http-equiv="Expires" content="0"/>' + LB
100 txt += '<meta http-equiv="X-UA-Compatible" content="IE=Edge"/>' + LB
101 txt += '<meta name="viewport" content="width=device-width, initial-scale=1">' + LB
102
103 # alternate links for different langauges
104 if @alternateLinksForLanguages?
105 for langId, url of @alternateLinksForLanguages
106 txt += '<link rel="alternate" hreflang="' + langId + '" href="' + url + '" />' + LB
107
108 # css and js
109 if @css != ''
110 txt += '<style type="text/css">' + LB + @css + LB + '</style>' + LB
111 txt += @htmlDocument.jsGetCollector('head').getAllAsTag()
112
113 # finish
114 txt += '</head>' + LB
115 return txt
116
117 pushCss: (css) ->
118 @css += css.trim() + LB
119
120class K.NateHtmlDocument extends K.NateHtml
121 constructor: (parent) ->
122 super(parent)
123 @theHead = new K.NateHtmlHead(@)
124 @theBody = new K.NateHtmlBody(@)
125 @jsPipes =
126 head: new K.JsCollector()
127 bodyEnd: new K.JsCollector()
128 onLoad: new K.JsCollector()
129 @resourcesToLoad =
130 css: []
131 js_head: []
132 js_bodyEnd: []
133 js_onLoad: []
134 @resourcesToLoadCnt = 0
135 @resourcesLoadDoneCb = null
136 @waitingToEmit = false
137 @langId = null
138
139 # debugging helpers
140 if 0
141 @LOG_resourceLoading = console.log
142 else
143 @LOG_resourceLoading = () -> ;
144
145 head: () ->
146 return @theHead
147
148 body: () ->
149 return @theBody
150
151 render: () ->
152 if @langId?
153 langMeta = ' lang="' + @langId + '"'
154 else
155 langMeta = ''
156
157 txt = '<!DOCTYPE HTML>' + LB
158 txt += '<html' + langMeta + '>' + LB
159 txt += @theHead.render()
160 txt += @theBody.render()
161 txt += '</html>' + LB
162 return txt
163
164 setLang: (langId) ->
165 @langId = langId
166
167 loadResource: (resourceData, done) ->
168 if RESOURCE_CACHE_ON and RESOURCE_CACHE[resourceData.path]?
169 @LOG_resourceLoading '[Nate] RESOURCE_CACHE hit !'
170 done(RESOURCE_CACHE[resourceData.path])
171 else
172 @LOG_resourceLoading '[Nate] RESOURCE_CACHE miss !'
173 fs.readFile resourceData.path, 'utf8', (err, data) =>
174 if not err?
175 RESOURCE_CACHE[resourceData.path] = data
176 done(data)
177 else
178 console.log err
179 throw new Error('File loading failed from path:' + resourceData.path)
180
181 # important:
182 # done() callback is not called at the moment the resource is loaded as this may cause change of order
183 # (for example, loading: A-B-C, then calling done(): A-C-B, may cause a lot of problems)
184 # done() is called in right order after ALL resources are loaded
185 addResourceToLoad: (pipe, resourceData, done) ->
186 # add load request to loading-pipe
187 @resourcesToLoadCnt++
188 resourceData.loaded = false
189 resourceData.done = done
190 @resourcesToLoad[pipe].push resourceData
191 @LOG_resourceLoading '[Nate] add resource:', resourceData.path
192
193 # get from cache or trigger loading resource
194 @loadResource resourceData, (data) =>
195 resourceData.loaded = true
196 @resourcesToLoadCnt--
197 @LOG_resourceLoading '[Nate] loaded resource:', resourceData.path
198 resourceData.loadedContent = data
199 @_resourcesCheckForResourcesReady()
200
201 _resourcesCheckForResourcesReady: () ->
202 if (@waitingToEmit) and (@resourcesToLoadCnt == 0)
203 if @resourcesLoadDoneCb?
204 @_resourcesLoadDoneCb()
205 @_resourcesLoadDoneCb = null
206
207 _resourcesLoadDoneCb: () ->
208 @LOG_resourceLoading '[Nate] All resources loaded...'
209 for resourceType of @resourcesToLoad
210 @resourcesToLoadItem = @resourcesToLoad[resourceType]
211 for resourceItem in @resourcesToLoadItem
212 resourceItem.done(resourceItem.loadedContent)
213 @resourcesLoadDoneCb?()
214
215 cssFromStaticFile: (path, done) ->
216 @addResourceToLoad 'css', {path:path}, (data) =>
217 @theHead.pushCss(data)
218 done?()
219
220 # supported pipes are:
221 # head - goes to <head></head>
222 # bodyEnd - goes to bottom of <body></body>
223 # onLoad - goes to jQuery onLoad handler at the end of <body></body> section
224 jsGetCollector: (pipeName) ->
225 return @jsPipes[pipeName]
226
227 jsFromStaticFile: (path, pipeName, done) ->
228 K.Error.reportIfParameterNotSet(path, 'javascript path')
229 @addResourceToLoad 'js_' + pipeName, {path:path}, (data) =>
230 @jsGetCollector(pipeName).pushFromTxt(data)
231 done?()
232
233 coffeeFromStaticFile: (path, pipeName, done) ->
234 K.Error.reportIfParameterNotSet(path, 'coffeeScript path')
235 @addResourceToLoad 'js_' + pipeName, {path:path}, (data) =>
236 # compile or get from cache
237 if RESOURCE_CACHE_ON and RESOURCE_COMPILED_CACHE[path]?
238 compiled = RESOURCE_COMPILED_CACHE[path]
239 else
240 compiled = coffee.compile(data)
241 RESOURCE_COMPILED_CACHE[path] = compiled
242 # emit and done()
243 @jsGetCollector(pipeName).pushFromTxt(compiled)
244 done?()
245
246 _onReadyToEmit: (done) ->
247 @waitingToEmit = true
248 @resourcesLoadDoneCb = done
249 @_resourcesCheckForResourcesReady()
250
251 emitAsResponse: (httpResponse, cb) ->
252 @_onReadyToEmit () =>
253 httpResponse.send @render()
254 cb?()
255
256 emitToString: (cb) ->
257 @_onReadyToEmit () =>
258 cb?(@render())
259
260class K.NateHtmlElem extends K.NateHtml
261 @NOTHING: 0
262 @JUST_CHILDREN: 1
263 @JUST_THE_INNER: 2
264 @OPEN_CLOSE_AT_ONCE: 3
265 @OPEN_THEN_CLOSE: 4
266 @ON_CLICK_EVENT_BLOCKER: 'var event=arguments[0] || window.event; var rv = (event.button != 0); event.returnValue = rv; return rv;'
267
268 constructor: (parent) ->
269 super(parent)
270 if parent?
271 parent.push(@)
272 else
273 @tagOpenCloseMode = K.NateHtmlElem.JUST_CHILDREN
274 @tagParamsList = {}
275 @enabled = true
276
277 deleteAllChildren: () ->
278 ; # TODO: implement this, but at the moment we need it only in K.NateHtmlDom
279
280 newNate: (parent) ->
281 return new K.NateHtmlElem(parent)
282
283 setTag: (@tagName, @tagParamsTxt, @tagInnerHTML, @tagOpenCloseMode) ->
284 if not @tagInnerHTML?
285 @tagInnerHTML = ''
286 if not @tagParamsTxt?
287 @tagParamsTxt = ''
288
289 tagOpenClose: (tag, txt, params) ->
290 rv = @newNate(@)
291 rv.setTag tag, params, txt, K.NateHtmlElem.OPEN_THEN_CLOSE
292 return rv
293
294 txt: (theTxt) ->
295 rv = @newNate(@)
296 rv.setTag null, null, theTxt, K.NateHtmlElem.JUST_THE_INNER
297 return rv
298
299 newTxt: (theTxt) ->
300 return @txt(theTxt)
301
302 br: () ->
303 return @txt '</br>'
304
305 newA: (txt, params) ->
306 return @tagOpenClose('a', txt, params)
307
308 newB: (txt, params) ->
309 return @tagOpenClose('b', txt, params)
310
311 newCanvas: (txt, params) ->
312 return @tagOpenClose('canvas', txt, params)
313
314 newDiv: (txt, params) ->
315 return @tagOpenClose('div', txt, params)
316
317 newH1: (txt, params) ->
318 return @tagOpenClose('h1', txt, params)
319
320 newH2: (txt, params) ->
321 return @tagOpenClose('h2', txt, params)
322
323 newH3: (txt, params) ->
324 return @tagOpenClose('h3', txt, params)
325
326 newImg: (src, params) ->
327 return @tagOpenClose('img', '', 'src="' + src + '" ' + params)
328
329 newInputCheckbox: (txt, params = '') ->
330 params = 'type="checkbox" ' + params
331 return @tagOpenClose('input', txt, params)
332
333 newInputPassword: (txt, params = '') ->
334 params = 'type="password" ' + params
335 return @tagOpenClose('input', txt, params)
336
337 newInputRadio: (txt, params = '') ->
338 params = 'type="radio" ' + params
339 return @tagOpenClose('input', txt, params)
340
341 newInputText: (txt, params = '') ->
342 params = 'type="text" ' + params
343 return @tagOpenClose('input', txt, params)
344
345 newInputSelect: (txt, params = '') ->
346 return @tagOpenClose('select', txt, params)
347
348 newLabel: (txt, params) ->
349 return @tagOpenClose('label', txt, params)
350
351 newLi: (txt, params) ->
352 return @tagOpenClose('li', txt, params)
353
354 newOption: (txt, params) ->
355 return @tagOpenClose('option', txt, params)
356
357 newP: (txt, params) ->
358 return @tagOpenClose('p', txt, params)
359
360 newSelect: (txt, params = '') ->
361 return @newInputSelect(txt, params)
362
363 newSpan: (txt, params) ->
364 return @tagOpenClose('span', txt, params)
365
366 newTextArea: (txt, params) ->
367 return @tagOpenClose('textarea', txt, params)
368
369 newUl: (txt, params) ->
370 return @tagOpenClose('ul', txt, params)
371
372 newTable: (txt, params) ->
373 return @tagOpenClose('table', txt, params)
374
375 newTBody: (txt, params) ->
376 return @tagOpenClose('tbody', txt, params)
377
378 newTr: (txt, params) ->
379 return @tagOpenClose('tr', txt, params)
380
381 newTd: (txt, params) ->
382 return @tagOpenClose('td', txt, params)
383
384 setParams: (params) ->
385 @tagParamsTxt = params
386
387 setText: (txt) ->
388 @tagInnerHTML = txt
389 return @
390
391 setOnClickEventBlocker: () ->
392 @set('onclick', K.NateHtmlElem.ON_CLICK_EVENT_BLOCKER)
393 return @
394
395 set: (param, value) ->
396 switch param
397 when 'innerHTML'
398 @tagInnerHTML = value
399 when 'checked', 'selected'
400 if value
401 @tagParamsList[param] = param
402 else
403 if @tagParamsList[param]?
404 delete @tagParamsList[param]
405 when 'class', 'href', 'id', 'for', 'name', 'onclick', 'style', 'value', \
406 'rowSpan', 'colSpan'
407 # TODO: value should be string-escaped
408 @tagParamsList[param] = value
409 when 'on', 'enabled'
410 @enabled = value
411
412 return @
413
414 setData: (key, value) ->
415 @tagParamsList['data-' + key] = value
416 return @
417
418 #
419 # Rendering part
420 #
421 render: () ->
422 if @enabled
423 switch @tagOpenCloseMode
424 when K.NateHtmlElem.JUST_THE_INNER
425 rv = @tagInnerHTML
426 rv += super()
427 when K.NateHtmlElem.OPEN_CLOSE_AT_ONCE, K.NateHtmlElem.OPEN_THEN_CLOSE
428 rv = '<' + @tagName
429 if @tagParamsTxt isnt ''
430 rv += ' ' + @tagParamsTxt
431 for paramKey of @tagParamsList
432 rv += ' ' + paramKey + '="' + @tagParamsList[paramKey] + '"'
433 if @tagOpenClose == K.NateHtmlElem.OPEN_CLOSE_AT_ONCE
434 rv += '/>'
435 else
436 rv += '>'
437 rv += super()
438 rv += @tagInnerHTML
439 rv += '</' + @tagName + '>' + LB
440 when K.NateHtmlElem.JUST_CHILDREN
441 rv = super()
442 else
443 rv = ''
444
445 return rv
446
447class K.NateHtmlBody extends K.NateHtmlElem
448 constructor: (parent) ->
449 super(parent)
450 @htmlDocument = parent
451 @tagOpenCloseMode = K.NateHtmlElem.JUST_CHILDREN
452
453 render: () ->
454 txt = '<body'
455 if @tagParamsTxt? and @tagParamsTxt isnt ''
456 txt += ' ' + @tagParamsTxt
457 txt += '>'
458 txt += super()
459 txt += @htmlDocument.jsGetCollector('bodyEnd').getAllAsTag()
460 txt += @htmlDocument.jsGetCollector('onLoad').getAllAsOnLoadCode()
461 txt += '</body>'
462 return txt