PageRenderTime 420ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/View/assets/plugins/notifyjs/src/notify.coffee

https://gitlab.com/MohammadShakil/POS_Management_System
CoffeeScript | 571 lines | 392 code | 102 blank | 77 comment | 86 complexity | c24c0c2827b3b30853bc40e833a99636 MD5 | raw file
  1. 'use strict'
  2. #plugin constants
  3. pluginName = 'notify'
  4. pluginClassName = pluginName+'js'
  5. blankFieldName = pluginName+"!blank"
  6. # ================================
  7. # POSITIONING
  8. # ================================
  9. positions =
  10. t: 'top'
  11. m: 'middle'
  12. b: 'bottom'
  13. l: 'left'
  14. c: 'center'
  15. r: 'right'
  16. hAligns = ['l','c','r']
  17. vAligns = ['t','m','b']
  18. mainPositions = ['t','b','l','r']
  19. #positions mapped to opposites
  20. opposites =
  21. t: 'b'
  22. m: null
  23. b: 't'
  24. l: 'r'
  25. c: null
  26. r: 'l'
  27. parsePosition = (str) ->
  28. pos = []
  29. $.each str.split(/\W+/), (i,word) ->
  30. w = word.toLowerCase().charAt(0)
  31. pos.push w if positions[w]
  32. pos
  33. # ================================
  34. # STYLES
  35. # ================================
  36. styles = {}
  37. coreStyle =
  38. name: 'core'
  39. html: """
  40. <div class="#{pluginClassName}-wrapper">
  41. <div class="#{pluginClassName}-arrow"></div>
  42. <div class="#{pluginClassName}-container"></div>
  43. </div>
  44. """
  45. # <div class="#{pluginClassName}-debug"></div>
  46. # .#{pluginClassName}-debug {
  47. # position: absolute;
  48. # border: 3px solid red;
  49. # height: 0;
  50. # width: 0;
  51. # }
  52. css: """
  53. .#{pluginClassName}-corner {
  54. position: fixed;
  55. margin: 5px;
  56. z-index: 1050;
  57. }
  58. .#{pluginClassName}-corner .#{pluginClassName}-wrapper,
  59. .#{pluginClassName}-corner .#{pluginClassName}-container {
  60. position: relative;
  61. display: block;
  62. height: inherit;
  63. width: inherit;
  64. margin: 3px;
  65. }
  66. .#{pluginClassName}-wrapper {
  67. z-index: 1;
  68. position: absolute;
  69. display: inline-block;
  70. height: 0;
  71. width: 0;
  72. }
  73. .#{pluginClassName}-container {
  74. display: none;
  75. z-index: 1;
  76. position: absolute;
  77. }
  78. .#{pluginClassName}-hidable {
  79. cursor: pointer;
  80. }
  81. [data-notify-text],[data-notify-html] {
  82. position: relative;
  83. }
  84. .#{pluginClassName}-arrow {
  85. position: absolute;
  86. z-index: 2;
  87. width: 0;
  88. height: 0;
  89. }
  90. """
  91. stylePrefixes =
  92. "border-radius": ["-webkit-", "-moz-"]
  93. getStyle = (name) ->
  94. styles[name]
  95. addStyle = (name, def) ->
  96. unless name
  97. throw "Missing Style name"
  98. unless def
  99. throw "Missing Style definition"
  100. unless def.html
  101. throw "Missing Style HTML"
  102. if styles[name]?.cssElem
  103. if window.console
  104. console.warn "#{pluginName}: overwriting style '#{name}'"
  105. styles[name].cssElem.remove()
  106. def.name = name
  107. styles[name] = def
  108. cssText = ""
  109. if def.classes
  110. $.each def.classes, (className, props) ->
  111. cssText += ".#{pluginClassName}-#{def.name}-#{className} {\n"
  112. $.each props, (name, val) ->
  113. #vendor prefixes
  114. if stylePrefixes[name]
  115. $.each stylePrefixes[name], (i, prefix) ->
  116. cssText += " #{prefix}#{name}: #{val};\n"
  117. #add prop
  118. cssText += " #{name}: #{val};\n"
  119. cssText += "}\n"
  120. if def.css
  121. cssText += """
  122. /* styles for #{def.name} */
  123. #{def.css}
  124. """
  125. if cssText
  126. def.cssElem = insertCSS cssText
  127. def.cssElem.attr('id', "notify-#{def.name}")
  128. #precompute usable text fields
  129. fields = {}
  130. elem = $(def.html)
  131. findFields 'html', elem, fields
  132. findFields 'text', elem, fields
  133. def.fields = fields
  134. insertCSS = (cssText) ->
  135. elem = createElem("style")
  136. elem.attr 'type', 'text/css'
  137. $("head").append elem
  138. try
  139. elem.html cssText
  140. catch e #ie fix
  141. elem[0].styleSheet.cssText = cssText
  142. elem
  143. # style.html helper
  144. findFields = (type, elem, fields) ->
  145. type = 'text' if type isnt 'html'
  146. attr = "data-notify-#{type}"
  147. find(elem,"[#{attr}]").each ->
  148. name = $(@).attr attr
  149. name = blankFieldName unless name
  150. fields[name] = type
  151. find = (elem, selector) ->
  152. if elem.is(selector) then elem else elem.find selector
  153. # ================================
  154. # OPTIONS
  155. # ================================
  156. #overridable options
  157. pluginOptions =
  158. clickToHide: true
  159. autoHide: true
  160. autoHideDelay: 5000
  161. arrowShow: true
  162. arrowSize: 5
  163. breakNewLines: true
  164. # autoReposition: true
  165. elementPosition: 'bottom'
  166. globalPosition: 'top right'
  167. style: 'bootstrap'
  168. className: 'error'
  169. showAnimation: 'slideDown'
  170. showDuration: 400
  171. hideAnimation: 'slideUp'
  172. hideDuration: 200
  173. gap: 5
  174. inherit = (a, b) ->
  175. F = () ->
  176. F.prototype = a
  177. $.extend true, new F(), b
  178. defaults = (opts) ->
  179. $.extend pluginOptions, opts
  180. # ================================
  181. # DOM MANIPULATION
  182. # ================================
  183. # plugin helpers
  184. createElem = (tag) ->
  185. $ "<#{tag}></#{tag}>"
  186. # references to global anchor positions
  187. globalAnchors = {}
  188. #gets first on n radios, and gets the fancy stylised input for hidden inputs
  189. getAnchorElement = (element) ->
  190. #choose the first of n radios
  191. if element.is('[type=radio]')
  192. radios = element.parents('form:first').find('[type=radio]').filter (i, e) ->
  193. $(e).attr('name') is element.attr('name')
  194. element = radios.first()
  195. #custom-styled inputs - find thier real element
  196. element
  197. incr = (obj, pos, val) ->
  198. # console.log "incr ---- #{pos} #{val} (#{typeof val})"
  199. if typeof val is 'string'
  200. val = parseInt val, 10
  201. else if typeof val isnt 'number'
  202. return
  203. return if isNaN val
  204. opp = positions[opposites[pos.charAt(0)]]
  205. temp = pos
  206. #use the opposite if exists
  207. if obj[opp] isnt `undefined`
  208. pos = positions[opp.charAt(0)]
  209. val = -val
  210. if obj[pos] is `undefined`
  211. obj[pos] = val
  212. else
  213. obj[pos] += val
  214. null
  215. realign = (alignment, inner, outer) ->
  216. return if alignment in ['l','t']
  217. 0
  218. else if alignment in ['c','m']
  219. outer/2 - inner/2
  220. else if alignment in ['r','b']
  221. outer - inner
  222. throw "Invalid alignment"
  223. encode = (text) ->
  224. encode.e = encode.e or createElem "div"
  225. encode.e.text(text).html()
  226. # ================================
  227. # NOTIFY CLASS
  228. # ================================
  229. #define plugin
  230. class Notification
  231. #setup instance variables
  232. constructor: (elem, data, options) ->
  233. options = {className: options} if typeof options is 'string'
  234. @options = inherit pluginOptions, if $.isPlainObject(options) then options else {}
  235. #load style html into @userContainer
  236. @loadHTML()
  237. @wrapper = $(coreStyle.html)
  238. @wrapper.addClass "#{pluginClassName}-hidable" if @options.clickToHide
  239. @wrapper.data pluginClassName, @
  240. @arrow = @wrapper.find ".#{pluginClassName}-arrow"
  241. @container = @wrapper.find ".#{pluginClassName}-container"
  242. @container.append @userContainer
  243. if elem and elem.length
  244. @elementType = elem.attr('type')
  245. @originalElement = elem
  246. @elem = getAnchorElement(elem)
  247. @elem.data pluginClassName, @
  248. # add into dom above elem
  249. @elem.before @wrapper
  250. @container.hide()
  251. @run(data)
  252. loadHTML: ->
  253. style = @getStyle()
  254. @userContainer = $(style.html)
  255. @userFields = style.fields
  256. show: (show, userCallback) ->
  257. callback = =>
  258. @destroy() if not show and not @elem
  259. userCallback() if userCallback
  260. hidden = @container.parent().parents(':hidden').length > 0
  261. elems = @container.add @arrow
  262. args = []
  263. if hidden and show
  264. fn = 'show'
  265. else if hidden and not show
  266. fn = 'hide'
  267. else if not hidden and show
  268. fn = @options.showAnimation
  269. args.push @options.showDuration
  270. else if not hidden and not show
  271. fn = @options.hideAnimation
  272. args.push @options.hideDuration
  273. else
  274. return callback()
  275. args.push callback
  276. elems[fn].apply elems, args
  277. setGlobalPosition: () ->
  278. [pMain, pAlign] = @getPosition()
  279. main = positions[pMain]
  280. align = positions[pAlign]
  281. key = pMain+"|"+pAlign
  282. anchor = globalAnchors[key]
  283. unless anchor
  284. anchor = globalAnchors[key] = createElem("div")
  285. css = {}
  286. css[main] = 0
  287. if align is 'middle'
  288. css.top = '45%'
  289. else if align is 'center'
  290. css.left = '45%'
  291. else
  292. css[align] = 0
  293. anchor.css(css).addClass("#{pluginClassName}-corner")
  294. $("body").append anchor
  295. anchor.prepend @wrapper
  296. setElementPosition: () ->
  297. position = @getPosition()
  298. [pMain, pAlign, pArrow] = position
  299. #grab some dimensions
  300. elemPos = @elem.position()
  301. elemH = @elem.outerHeight()
  302. elemW = @elem.outerWidth()
  303. elemIH = @elem.innerHeight()
  304. elemIW = @elem.innerWidth()
  305. wrapPos = @wrapper.position()
  306. contH = @container.height()
  307. contW = @container.width()
  308. #start calculations
  309. mainFull = positions[pMain]
  310. opp = opposites[pMain]
  311. oppFull = positions[opp]
  312. #initial positioning
  313. css = {}
  314. css[oppFull] = if pMain is 'b'
  315. elemH
  316. else if pMain is 'r'
  317. elemW
  318. else
  319. 0
  320. #correct for elem-wrapper offset
  321. # unless navigator.userAgent.match /MSIE/
  322. incr css, 'top', elemPos.top - wrapPos.top
  323. incr css, 'left', elemPos.left - wrapPos.left
  324. #correct for margins
  325. for pos in ['top', 'left']
  326. margin = parseInt @elem.css("margin-#{pos}"), 10
  327. incr css, pos, margin if margin
  328. #correct for paddings (only for inline)
  329. # if /^inline/.test @elem.css('display')
  330. # padding = parseInt @elem.css("padding-#{pos}"), 10
  331. # incr css, pos, -padding if padding
  332. #add gap
  333. gap = Math.max 0, @options.gap - if @options.arrowShow then arrowSize else 0
  334. incr css, oppFull, gap
  335. #calculate arrow
  336. if not @options.arrowShow
  337. @arrow.hide()
  338. else
  339. arrowSize = @options.arrowSize
  340. arrowCss = $.extend {}, css
  341. arrowColor = @userContainer.css("border-color") or
  342. @userContainer.css("background-color") or
  343. 'white'
  344. #build arrow
  345. for pos in mainPositions
  346. posFull = positions[pos]
  347. continue if pos is opp
  348. color = if posFull is mainFull then arrowColor else 'transparent'
  349. arrowCss["border-#{posFull}"] = "#{arrowSize}px solid #{color}"
  350. #add some room for the arrow
  351. incr css, positions[opp], arrowSize
  352. incr arrowCss, positions[pAlign], arrowSize*2 if pAlign in mainPositions
  353. #calculate container alignment
  354. if pMain in vAligns
  355. incr css, 'left', realign(pAlign, contW, elemW)
  356. incr arrowCss, 'left', realign(pAlign, arrowSize, elemIW) if arrowCss
  357. else if pMain in hAligns
  358. incr css, 'top', realign(pAlign, contH, elemH)
  359. incr arrowCss, 'top', realign(pAlign, arrowSize, elemIH) if arrowCss
  360. css.display = 'block' if @container.is(":visible")
  361. #apply css
  362. @container.removeAttr('style').css css
  363. @arrow.removeAttr('style').css(arrowCss) if arrowCss
  364. getPosition: ->
  365. text = @options.position or if @elem then @options.elementPosition else @options.globalPosition
  366. pos = parsePosition text
  367. #validate position
  368. pos[0] = 'b' if pos.length is 0
  369. if pos[0] not in mainPositions
  370. throw "Must be one of [#{mainPositions}]"
  371. #validate alignment
  372. if pos.length is 1 or
  373. (pos[0] in vAligns and pos[1] not in hAligns) or
  374. (pos[0] in hAligns and pos[1] not in vAligns)
  375. pos[1] = if pos[0] in hAligns then 'm' else 'l'
  376. if pos.length is 2
  377. pos[2] = pos[1]
  378. return pos
  379. getStyle: (name) ->
  380. name = @options.style unless name
  381. name = 'default' unless name
  382. style = styles[name]
  383. throw "Missing style: #{name}"unless style
  384. style
  385. updateClasses: ->
  386. classes = ['base']
  387. if $.isArray @options.className
  388. classes = classes.concat @options.className
  389. else if @options.className
  390. classes.push @options.className
  391. style = @getStyle()
  392. classes = $.map(classes, (n) -> "#{pluginClassName}-#{style.name}-#{n}").join ' '
  393. @userContainer.attr 'class', classes
  394. #run plugin
  395. run: (data, options) ->
  396. #update options
  397. if $.isPlainObject(options)
  398. $.extend @options, options
  399. #shortcut special case
  400. else if $.type(options) is 'string'
  401. @options.className = options
  402. if @container and not data
  403. @show false
  404. return
  405. else if not @container and not data
  406. return
  407. datas = {}
  408. if $.isPlainObject data
  409. datas = data
  410. else
  411. datas[blankFieldName] = data
  412. for name, d of datas
  413. type = @userFields[name]
  414. continue unless type
  415. if type is 'text'
  416. #escape
  417. d = encode(d)
  418. d = d.replace(/\n/g,'<br/>') if @options.breakNewLines
  419. #update content
  420. value = if name is blankFieldName then '' else '='+name
  421. find(@userContainer,"[data-notify-#{type}#{value}]").html(d)
  422. #set styles
  423. @updateClasses()
  424. #positioning
  425. if @elem
  426. @setElementPosition()
  427. else
  428. @setGlobalPosition()
  429. @show true
  430. #autohide
  431. if @options.autoHide
  432. clearTimeout @autohideTimer
  433. @autohideTimer = setTimeout =>
  434. @show false
  435. , @options.autoHideDelay
  436. destroy: ->
  437. @wrapper.remove()
  438. # ================================
  439. # API
  440. # ================================
  441. # $.pluginName( { ... } ) changes options for all instances
  442. $[pluginName] = (elem, data, options) ->
  443. if (elem and elem.nodeName) or elem.jquery
  444. $(elem)[pluginName](data, options)
  445. else
  446. options = data
  447. data = elem
  448. new Notification null, data, options
  449. elem
  450. # $( ... ).pluginName( { .. } ) creates a cached instance on each
  451. $.fn[pluginName] = (data, options) ->
  452. $(@).each ->
  453. inst = getAnchorElement($(@)).data(pluginClassName)
  454. if inst
  455. inst.run data, options
  456. else
  457. new Notification $(@), data, options
  458. @
  459. #extra methods
  460. $.extend $[pluginName], { defaults, addStyle, pluginOptions, getStyle, insertCSS }
  461. #when ready
  462. $ ->
  463. #insert default style
  464. insertCSS(coreStyle.css).attr('id', 'core-notify')
  465. #watch all notifications clicks
  466. $(document).on 'click', ".#{pluginClassName}-hidable", (e) ->
  467. $(@).trigger 'notify-hide'
  468. $(document).on 'notify-hide', ".#{pluginClassName}-wrapper", (e) ->
  469. $(@).data(pluginClassName)?.show false