Extract CoffeeScript view helper methods into their own mixin

This commit is contained in:
Ryan Lue 2019-02-27 15:42:56 +08:00 committed by Thorsten Eckel
parent 17fd53f175
commit 48352ece6d
4 changed files with 283 additions and 195 deletions

View file

@ -10,200 +10,6 @@
#= require_tree ./lib/app_post #= require_tree ./lib/app_post
class App extends Spine.Controller class App extends Spine.Controller
helper =
# define print name helper
P: (object, attributeName, attributes, table = false) ->
App.viewPrint(object, attributeName, attributes, table)
# define date format helper
date: (time) ->
return '' if !time
timeObject = new Date(time)
d = App.Utils.formatTime(timeObject.getDate(), 2)
m = App.Utils.formatTime(timeObject.getMonth() + 1, 2)
y = timeObject.getFullYear()
"#{y}-#{m}-#{d}"
# define datetime format helper
datetime: (time) ->
return '' if !time
timeObject = new Date(time)
d = App.Utils.formatTime(timeObject.getDate(), 2)
m = App.Utils.formatTime(timeObject.getMonth() + 1, 2)
y = timeObject.getFullYear()
S = App.Utils.formatTime(timeObject.getSeconds(), 2)
M = App.Utils.formatTime(timeObject.getMinutes(), 2)
H = App.Utils.formatTime(timeObject.getHours(), 2)
"#{y}-#{m}-#{d} #{H}:#{M}:#{S}"
# define decimal format helper
decimal: (data, positions = 2) ->
App.Utils.decimal(data, positions)
# define time_duration / mm:ss / hh:mm:ss format helper
time_duration: (time) ->
return '' if !time
return '' if isNaN(parseInt(time))
# Hours, minutes and seconds
hrs = ~~parseInt((time / 3600))
mins = ~~parseInt(((time % 3600) / 60))
secs = parseInt(time % 60)
# Output like "1:01" or "4:03:59" or "123:03:59"
mins = "0#{mins}" if mins < 10
secs = "0#{secs}" if secs < 10
if hrs > 0
return "#{hrs}:#{mins}:#{secs}"
"#{mins}:#{secs}"
# define mask helper
# mask an value like 'a***********yz'
M: (item, start = 1, end = 2) ->
return '' if !item
string = ''
end = item.length - end - 1
for n in [0..item.length-1]
if start <= n && end >= n
string += '*'
else
string += item[n]
string
# define translation helper
T: (item, args...) ->
App.i18n.translateContent(item, args...)
# define translation inline helper
Ti: (item, args...) ->
App.i18n.translateInline(item, args...)
# define translation for date helper
Tdate: (item, args...) ->
App.i18n.translateDate(item, args...)
# define translation for timestamp helper
Ttimestamp: (item, args...) ->
App.i18n.translateTimestamp(item, args...)
# define linkify helper
L: (item) ->
if item && typeof item is 'string'
return App.Utils.linkify(item)
item
# define config helper
C: (key) ->
App.Config.get(key)
# define session helper
S: (key) ->
App.Session.get(key)
# define view helper for rendering partial views
V: (name, params) ->
App.view(name)(params)
# define address line helper
AddressLine: (line) ->
return '' if !line
items = emailAddresses.parseAddressList(line)
# line was not parsable
return App.Utils.htmlEscape(line) if !items
# set markup
result = ''
for item in items
if result
result = result + ', '
if item.name
item.name = item.name
.replace(',', '')
.replace(';', '')
.replace('"', '')
.replace('\'', '')
if item.name.match(/\@|,|;|\^|\+|#|§|\$|%|&|\/|\(|\)|=|\?|\*/)
item.name = "\"#{item.name}\""
result = "#{result}#{App.Utils.htmlEscape(item.name)} "
if item.address
result = result + " <span class=\"text-muted\">&lt;#{App.Utils.htmlEscape(item.address)}&gt</span>"
result
# define file size helper
humanFileSize: (size) ->
App.Utils.humanFileSize(size)
# define pretty/human time helper
humanTime: (time, escalation = false, cssClass = '') ->
timestamp = App.i18n.translateTimestamp(time)
if escalation
cssClass += ' escalation'
humanTime = App.PrettyDate.humanTime(time, escalation)
"<time class=\"humanTimeFromNow #{cssClass}\" data-time=\"#{time}\" title=\"#{timestamp}\">#{humanTime}</time>"
# define icon helper
Icon: (name, className = '') ->
App.Utils.icon(name, className)
# define richtext helper
RichText: (string) ->
return string if !string
if string.match(/@T\('/)
string = string.replace(/@T\('(.+?)'\)/g, (match, capture) ->
App.i18n.translateContent(capture)
)
return marked(string)
App.i18n.translateContent(string)
ContentTypeIcon: (contentType) ->
contentType = App.Utils.contentTypeCleanup(contentType)
icons =
# image
'image/jpeg': 'file-image'
'image/jpg': 'file-image'
'image/png': 'file-image'
'image/svg': 'file-image'
'image/gif': 'file-image'
# documents
'application/pdf': 'file-pdf'
'application/msword': 'file-word' # .doc, .dot
'application/vnd.ms-word': 'file-word'
'application/vnd.oasis.opendocument.text': 'file-word'
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'file-word' # .docx
'application/vnd.openxmlformats-officedocument.wordprocessingml.template': 'file-word' # .dotx
'application/vnd.ms-excel': 'file-excel' # .xls
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'file-excel' # .xlsx
'application/vnd.oasis.opendocument.spreadsheet': 'file-excel'
'application/vnd.ms-powerpoint': 'file-powerpoint' # .ppt
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'file-powerpoint' # .pptx
'application/vnd.oasis.opendocument.presentation': 'file-powerpoint'
'text/plain': 'file-text'
'text/html': 'file-code'
'application/json': 'file-code'
'message/rfc822': 'file-email'
# code
'application/json': 'file-code'
# text
'text/plain': 'file-text'
'text/rtf': 'file-text'
# archives
'application/gzip': 'file-archive'
'application/zip': 'file-archive'
return icons[contentType]
canDownload: (contentType) ->
contentType = App.Utils.contentTypeCleanup(contentType)
contentType != 'text/html'
canPreview: (contentType) ->
return false if _.isEmpty(contentType)
return true if contentType.match(/image\/(png|jpg|jpeg|gif)/i)
false
@viewPrint: (object, attributeName, attributes, table) -> @viewPrint: (object, attributeName, attributes, table) ->
if !attributes if !attributes
attributes = {} attributes = {}
@ -342,7 +148,7 @@ class App extends Spine.Controller
@view: (name) -> @view: (name) ->
template = (params = {}) -> template = (params = {}) ->
JST["app/views/#{name}"](_.extend(params, helper)) JST["app/views/#{name}"](_.extend(params, App.ViewHelpers))
template template
class App.UiElement class App.UiElement

View file

@ -952,6 +952,34 @@ class App.Utils
path = if window.svgPolyfill then '' else 'assets/images/icons.svg' path = if window.svgPolyfill then '' else 'assets/images/icons.svg'
"<svg class=\"icon icon-#{name} #{className}\"><use xlink:href=\"#{path}#icon-#{name}\" /></svg>" "<svg class=\"icon icon-#{name} #{className}\"><use xlink:href=\"#{path}#icon-#{name}\" /></svg>"
@fontIcon: (name, font) ->
@loadIconFont(font)
"<i class=\"icon\" data-font=\"#{font}\">#{String.fromCharCode('0x'+ name)}</i>"
@loadIconFont: (font) ->
el = $("[data-icon-font=\"#{font}\"]")
return if el.length # already loaded
el = $("<style data-icon-font=\"#{font}\">").appendTo('head')
woffUrl = "assets/icon-fonts/#{font}.woff"
css = """
@font-face {
font-family: '#{font}';
src: url('#{woffUrl}');
font-weight: normal;
font-style: normal;
}
[data-font="#{font}"] {
font-family: '#{font}';
}
"""
el.text css
@loadIconFontInfo: (font, callback) ->
$.getJSON "assets/icon-fonts/#{font}.json", (data) -> callback(data.icons)
@getScrollBarWidth: -> @getScrollBarWidth: ->
$outer = $('<div>').css( $outer = $('<div>').css(
visibility: 'hidden' visibility: 'hidden'

View file

@ -0,0 +1,201 @@
# Top-level shortcuts for various view template helper methods,
# to be included in view templates via
#
# JST["path/to/template"](_.extend(App.ViewHelpers))
App.ViewHelpers =
# define print name helper
P: (object, attributeName, attributes, table = false) ->
App.viewPrint(object, attributeName, attributes, table)
# define date format helper
date: (time) ->
return '' if !time
timeObject = new Date(time)
d = App.Utils.formatTime(timeObject.getDate(), 2)
m = App.Utils.formatTime(timeObject.getMonth() + 1, 2)
y = timeObject.getFullYear()
"#{y}-#{m}-#{d}"
# define datetime format helper
datetime: (time) ->
return '' if !time
timeObject = new Date(time)
d = App.Utils.formatTime(timeObject.getDate(), 2)
m = App.Utils.formatTime(timeObject.getMonth() + 1, 2)
y = timeObject.getFullYear()
S = App.Utils.formatTime(timeObject.getSeconds(), 2)
M = App.Utils.formatTime(timeObject.getMinutes(), 2)
H = App.Utils.formatTime(timeObject.getHours(), 2)
"#{y}-#{m}-#{d} #{H}:#{M}:#{S}"
# define decimal format helper
decimal: (data, positions = 2) ->
App.Utils.decimal(data, positions)
# define time_duration / mm:ss / hh:mm:ss format helper
time_duration: (time) ->
return '' if !time
return '' if isNaN(parseInt(time))
# Hours, minutes and seconds
hrs = ~~parseInt((time / 3600))
mins = ~~parseInt(((time % 3600) / 60))
secs = parseInt(time % 60)
# Output like "1:01" or "4:03:59" or "123:03:59"
mins = "0#{mins}" if mins < 10
secs = "0#{secs}" if secs < 10
if hrs > 0
return "#{hrs}:#{mins}:#{secs}"
"#{mins}:#{secs}"
# define mask helper
# mask an value like 'a***********yz'
M: (item, start = 1, end = 2) ->
return '' if !item
string = ''
end = item.length - end - 1
for n in [0..item.length-1]
if start <= n && end >= n
string += '*'
else
string += item[n]
string
# define translation helper
T: (item, args...) ->
App.i18n.translateContent(item, args...)
# define translation inline helper
Ti: (item, args...) ->
App.i18n.translateInline(item, args...)
# define translation for date helper
Tdate: (item, args...) ->
App.i18n.translateDate(item, args...)
# define translation for timestamp helper
Ttimestamp: (item, args...) ->
App.i18n.translateTimestamp(item, args...)
# define linkify helper
L: (item) ->
if item && typeof item is 'string'
return App.Utils.linkify(item)
item
# define config helper
C: (key) ->
App.Config.get(key)
# define session helper
S: (key) ->
App.Session.get(key)
# define view helper for rendering partial views
V: (name, params) ->
App.view(name)(params)
# define address line helper
AddressLine: (line) ->
return '' if !line
items = emailAddresses.parseAddressList(line)
# line was not parsable
return App.Utils.htmlEscape(line) if !items
# set markup
result = ''
for item in items
if result
result = result + ', '
if item.name
item.name = item.name
.replace(',', '')
.replace(';', '')
.replace('"', '')
.replace('\'', '')
if item.name.match(/\@|,|;|\^|\+|#|§|\$|%|&|\/|\(|\)|=|\?|\*/)
item.name = "\"#{item.name}\""
result = "#{result}#{App.Utils.htmlEscape(item.name)} "
if item.address
result = result + " <span class=\"text-muted\">&lt;#{App.Utils.htmlEscape(item.address)}&gt</span>"
result
# define file size helper
humanFileSize: (size) ->
App.Utils.humanFileSize(size)
# define pretty/human time helper
humanTime: (time, escalation = false, cssClass = '') ->
timestamp = App.i18n.translateTimestamp(time)
if escalation
cssClass += ' escalation'
humanTime = App.PrettyDate.humanTime(time, escalation)
"<time class=\"humanTimeFromNow #{cssClass}\" data-time=\"#{time}\" title=\"#{timestamp}\">#{humanTime}</time>"
# Why not just use `Icon: App.Utils.icon`?
# Because App.Utils isn't loaded until after this file.
Icon: (name, className = '') ->
App.Utils.icon(name, className)
fontIcon: (name, font) ->
App.Utils.fontIcon(name, font)
# define richtext helper
RichText: (string) ->
return string if !string
if string.match(/@T\('/)
string = string.replace(/@T\('(.+?)'\)/g, (match, capture) ->
App.i18n.translateContent(capture)
)
return marked(string)
App.i18n.translateContent(string)
ContentTypeIcon: (contentType) ->
contentType = App.Utils.contentTypeCleanup(contentType)
icons =
# image
'image/jpeg': 'file-image'
'image/jpg': 'file-image'
'image/png': 'file-image'
'image/svg': 'file-image'
'image/gif': 'file-image'
# documents
'application/pdf': 'file-pdf'
'application/msword': 'file-word' # .doc, .dot
'application/vnd.ms-word': 'file-word'
'application/vnd.oasis.opendocument.text': 'file-word'
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'file-word' # .docx
'application/vnd.openxmlformats-officedocument.wordprocessingml.template': 'file-word' # .dotx
'application/vnd.ms-excel': 'file-excel' # .xls
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'file-excel' # .xlsx
'application/vnd.oasis.opendocument.spreadsheet': 'file-excel'
'application/vnd.ms-powerpoint': 'file-powerpoint' # .ppt
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'file-powerpoint' # .pptx
'application/vnd.oasis.opendocument.presentation': 'file-powerpoint'
'text/plain': 'file-text'
'text/html': 'file-code'
'application/json': 'file-code'
'message/rfc822': 'file-email'
# code
'application/json': 'file-code'
# text
'text/plain': 'file-text'
'text/rtf': 'file-text'
# archives
'application/gzip': 'file-archive'
'application/zip': 'file-archive'
return icons[contentType]
canDownload: (contentType) ->
contentType = App.Utils.contentTypeCleanup(contentType)
contentType != 'text/html'
canPreview: (contentType) ->
return false if _.isEmpty(contentType)
return true if contentType.match(/image\/(png|jpg|jpeg|gif)/i)
false

View file

@ -3151,6 +3151,59 @@ test("htmlImage2DataUrl", function() {
}); });
test('App.Utils.icon()', function() {
// When given no arguments,
// expect @icon() to return null
equal(App.Utils.icon(), null, 'with no arguments')
// On a modern browser and when given a single argument,
// expect @icon(name) to return an <svg> tag
window.svgPolyfill = false
svgTag = '<svg class="icon icon-foo "><use xlink:href="assets/images/icons.svg#icon-foo" /></svg>'
equal(App.Utils.icon('foo'), svgTag, 'with one arg / no SVG polyfill')
// On a modern browser and when given two arguments,
// expect @icon(name) to return an <svg> tag
// with second arg as add'l class name
window.svgPolyfill = false
svgTag = '<svg class="icon icon-foo bar"><use xlink:href="assets/images/icons.svg#icon-foo" /></svg>'
equal(App.Utils.icon('foo', 'bar'), svgTag, 'with two args / no SVG polyfill')
// On a browser requiring SVG polyfill and when given a single argument,
// expect @icon(name, class) to return an <svg> tag
// with pathless xlink:href attr
window.svgPolyfill = true
svgTag = '<svg class="icon icon-foo "><use xlink:href="#icon-foo" /></svg>'
equal(App.Utils.icon('foo'), svgTag, 'with one arg / SVG polyfill')
// On a browser requiring SVG polyfill and when given two arguments,
// expect @icon(name, class) to return an <svg> tag
// with pathless xlink:href attr and second arg as add'l class name
window.svgPolyfill = true
svgTag = '<svg class="icon icon-foo bar"><use xlink:href="#icon-foo" /></svg>'
equal(App.Utils.icon('foo', 'bar'), svgTag, 'with two args / SVG polyfill')
// For a left-to-right browser language and when given an argument containing '{start}' or '{end}',
// expect @icon(name) to return an <svg> tag
// replacing '{start}' with 'left' and '{end}' with 'right'
window.svgPolyfill = false
App.i18n.dir = function() { return 'ltr' }
svgTag = '<svg class="icon icon-arrow-left "><use xlink:href="assets/images/icons.svg#icon-arrow-left" /></svg>'
equal(App.Utils.icon('arrow-{start}'), svgTag, 'for ltr locale / name includes "{start}"')
svgTag = '<svg class="icon icon-arrow-right "><use xlink:href="assets/images/icons.svg#icon-arrow-right" /></svg>'
equal(App.Utils.icon('arrow-{end}'), svgTag, 'for ltr locale / name includes "{end}"')
// For a right-to-left browser language and when given an argument containing '{start}' or '{end}',
// expect @icon(name) to return an <svg> tag
// replacing '{start}' with 'left' and '{end}' with 'right'
window.svgPolyFill = false
App.i18n.dir = function() { return 'rtl' }
svgTag = '<svg class="icon icon-arrow-right "><use xlink:href="assets/images/icons.svg#icon-arrow-right" /></svg>'
equal(App.Utils.icon('arrow-{start}'), svgTag, 'for rtl locale / name includes "{start}"')
svgTag = '<svg class="icon icon-arrow-left "><use xlink:href="assets/images/icons.svg#icon-arrow-left" /></svg>'
equal(App.Utils.icon('arrow-{end}'), svgTag, 'for rtl locale / name includes "{end}"')
});
source = '<img src="/assets/images/avatar-bg.png">some test' source = '<img src="/assets/images/avatar-bg.png">some test'
$('#image2text').html(source) $('#image2text').html(source)
var htmlImage2DataUrlTest = function() { var htmlImage2DataUrlTest = function() {