'use strict' #plugin constants pluginName = 'notify' className = '__'+pluginName positions = t: 'top' m: 'middle' b: 'bottom' l: 'left' c: 'center' r: 'right' hAligns = ['l','c','r'] vAligns = ['t','m','b'] mainPositions = ['t','b','l','r'] #positions mapped to opposites opposites = t: 'b' m: null b: 't' l: 'r' c: null r: 'l' parsePosition = (str) -> pos = [] $.each str.split(/\W+/), (i,word) -> w = word.toLowerCase().charAt(0) pos.push w if positions[w] pos #built-in styles styles = core: html: """
""" #
# .#{className}Debug { # position: absolute; # border: 3px solid red; # height: 0; # width: 0; # } css: """ .#{className}Corner { position: fixed; top: 0; right: 0; margin: 5px; z-index: 1050; } .#{className}Corner .#{className}Wrapper, .#{className}Corner .#{className}Container { position: relative; display: block; height: inherit; width: inherit; } .#{className}Wrapper { z-index: 1; position: absolute; display: inline-block; height: 0; width: 0; } .#{className}Container { display: none; z-index: 1; position: absolute; cursor: pointer; } .#{className}Text { position: relative; } .#{className}Arrow { position: absolute; z-index: 2; width: 0; height: 0; } """ user: bootstrap: html: """
""" classes: base: """ padding: 8px 35px 8px 14px; margin-bottom: 20px; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); background-color: #fcf8e3; border: 1px solid #fbeed5; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; """ error: info: warn: bootstrap: html: """
""" css: """ .#{className}Bootstrap { white-space: nowrap; margin: 5px !important; padding-left: 25px !important; background-repeat: no-repeat; background-position: 3px 7px; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAtRJREFUeNqkVc1u00AQHq+dOD+0poIQfkIjalW0SEGqRMuRnHos3DjwAH0ArlyQeANOOSMeAA5VjyBxKBQhgSpVUKKQNGloFdw4cWw2jtfMOna6JOUArDTazXi/b3dm55socPqQhFka++aHBsI8GsopRJERNFlY88FCEk9Yiwf8RhgRyaHFQpPHCDmZG5oX2ui2yilkcTT1AcDsbYC1NMAyOi7zTX2Agx7A9luAl88BauiiQ/cJaZQfIpAlngDcvZZMrl8vFPK5+XktrWlx3/ehZ5r9+t6e+WVnp1pxnNIjgBe4/6dAysQc8dsmHwPcW9C0h3fW1hans1ltwJhy0GxK7XZbUlMp5Ww2eyan6+ft/f2FAqXGK4CvQk5HueFz7D6GOZtIrK+srupdx1GRBBqNBtzc2AiMr7nPplRdKhb1q6q6zjFhrklEFOUutoQ50xcX86ZlqaZpQrfbBdu2R6/G19zX6XSgh6RX5ubyHCM8nqSID6ICrGiZjGYYxojEsiw4PDwMSL5VKsC8Yf4VRYFzMzMaxwjlJSlCyAQ9l0CW44PBADzXhe7xMdi9HtTrdYjFYkDQL0cn4Xdq2/EAE+InCnvADTf2eah4Sx9vExQjkqXT6aAERICMewd/UAp/IeYANM2joxt+q5VI+ieq2i0Wg3l6DNzHwTERPgo1ko7XBXj3vdlsT2F+UuhIhYkp7u7CarkcrFOCtR3H5JiwbAIeImjT/YQKKBtGjRFCU5IUgFRe7fF4cCNVIPMYo3VKqxwjyNAXNepuopyqnld602qVsfRpEkkz+GFL1wPj6ySXBpJtWVa5xlhpcyhBNwpZHmtX8AGgfIExo0ZpzkWVTBGiXCSEaHh62/PoR0p/vHaczxXGnj4bSo+G78lELU80h1uogBwWLf5YlsPmgDEd4M236xjm+8nm4IuE/9u+/PH2JXZfbwz4zw1WbO+SQPpXfwG/BBgAhCNZiSb/pOQAAAAASUVORK5CYII=); } """ colors: red: '#eed3d7' #overridable options pluginOptions = autoHide: false autoHideDelay: 2000 arrowShow: true arrowSize: 5 position: 'bottom' # Default style style: 'default' # Default color color: 'black' # Color mappings colors: red: '#b94a48' green: '#33be40' black: '#393939' blue: '#00f' showAnimation: 'slideDown' showDuration: 400 hideAnimation: 'slideUp' hideDuration: 200 # Gap between main and element offsetY: 0 offsetX: 0 #TODO add z-index watches #parents: { '.ui-dialog': 5001 } # plugin helpers createElem = (tag) -> $ "<#{tag}>" inherit = (a, b) -> F = () -> F.prototype = a $.extend true, new F(), b # container for element-less notifications cornerElem = createElem("div").addClass("#{className}Corner") #gets first on n radios, and gets the fancy stylised input for hidden inputs getAnchorElement = (element) -> #choose the first of n radios if element.is('[type=radio]') radios = element.parents('form:first').find('[type=radio]').filter (i, e) -> $(e).attr('name') is element.attr('name') element = radios.first() #custom-styled inputs - find thier real element element incr = (obj, pos, val) -> # console.log "incr ---- #{pos} #{val} (#{typeof val})" if typeof val is 'string' val = parseInt val, 10 else if typeof val isnt 'number' return return if isNaN val opp = positions[opposites[pos.charAt(0)]] temp = pos #use the opposite if exists if obj[opp] isnt `undefined` pos = positions[opp.charAt(0)] val *= -1 if obj[pos] is `undefined` obj[pos] = val else obj[pos] += val null realign = (alignment, inner, outer) -> return if alignment in ['l','t'] 0 else if alignment in ['c','m'] outer/2 - inner/2 else if alignment in ['r','b'] outer - inner throw "Invalid alignment" insertCSS = (style) -> return unless style and style.css elem = style.cssElem return if elem elem = createElem("style") $("head").append elem style.cssElem = elem try elem.html style.css catch e #ie fix elem[0].styleSheet.cssText = style.css #define plugin class Notification #setup instance variables constructor: (elem, data, options) -> options = {color: options} if typeof options is 'string' @options = inherit pluginOptions, if $.isPlainObject(options) then options else {} #load user css into dom @loadCSS() #load user html into @userContainer @loadHTML() @wrapper = $(styles.core.html) @wrapper.data className, @ @arrow = @wrapper.find ".#{className}Arrow" @container = @wrapper.find ".#{className}Container" @container.append @userContainer if elem and elem.length @elementType = elem.attr('type') @originalElement = elem @elem = getAnchorElement(elem) @elem.data className, @ # add into dom above elem @elem.before @wrapper else # @options.autoHide = true @options.arrowShow = false cornerElem.prepend @wrapper @container.hide() @run(data) loadCSS: (style) -> unless style name = @options.style style = @getStyle(name) #insert insertCSS style loadHTML: -> style = @getStyle() @userContainer = $(style.html) @text = @userContainer.find '[data-notify-text]' if @text.length is 0 throw "style: #{name} HTML is missing the: 'data-notify-text' attribute" @text.addClass "#{className}Text" show: (show, callback = $.noop) -> hidden = @container.parent().parents(':hidden').length > 0 elems = @container.add @arrow args = [] if hidden and show fn = 'show' else if hidden and not show fn = 'hide' else if not hidden and show fn = @options.showAnimation args.push @options.showDuration else if not hidden and not show fn = @options.hideAnimation args.push @options.hideDuration else return callback() args.push callback elems[fn].apply elems, args updatePosition: -> return unless @elem #grab some dimensions elemPos = @elem.position() elemH = @elem.outerHeight() elemW = @elem.outerWidth() elemIH = @elem.innerHeight() elemIW = @elem.innerWidth() wrapPos = @wrapper.position() contH = @container.height() contW = @container.width() #get user defined position position = @getPosition() pMain = position[0] pAlign = position[1] pArrow = position[2] or pAlign #start calculations mainFull = positions[pMain] opp = opposites[pMain] oppFull = positions[opp] #initial positioning css = {} css[oppFull] = if pMain is 'b' then elemH else if pMain is 'r' then elemW else 0 #correct for elem-wrapper offset # unless navigator.userAgent.match /MSIE/ # incr css, 'top', (elemPos.top - wrapPos.top) incr css, 'left', elemPos.left - wrapPos.left #correct for left margin margin = parseInt @elem.css("margin-left"), 10 incr css, 'left', margin if margin #correct for inline top padding if /^inline/.test @elem.css('display') padding = parseInt @elem.css("padding-top"), 10 incr css, 'top', -padding if padding #user defined offset incr css, 'top', @options.offsetY incr css, 'left', @options.offsetX #calculate arrow if not @options.arrowShow @arrow.hide() else arrowSize = @options.arrowSize arrowCss = $.extend {}, css #build arrow for pos in mainPositions posFull = positions[pos] continue if pos is opp color = if posFull is mainFull then @getColor() else 'transparent' arrowCss["border-#{posFull}"] = "#{arrowSize}px solid #{color}" #add some room for the arrow incr css, positions[opp], arrowSize incr arrowCss, positions[pAlign], arrowSize*2 if pAlign in mainPositions #calculate container alignment if pMain in vAligns incr css, 'left', realign(pAlign, contW, elemW) incr arrowCss, 'left', realign(pAlign, arrowSize, elemIW) if arrowCss else if pMain in hAligns incr css, 'top', realign(pAlign, contH, elemH) incr arrowCss, 'top', realign(pAlign, arrowSize, elemIH) if arrowCss #apply css css.display = 'block' if @container.is(":visible") @container.removeAttr('style').css css @arrow.removeAttr('style').css(arrowCss) if arrowCss getPosition: -> text = @options.position pos = parsePosition text #validate position pos[0] = 'b' if pos.length is 0 if pos[0] not in mainPositions throw "Must be one of [#{mainPositions}]" #validate alignment if pos.length is 1 or (pos[0] in vAligns and pos[1] not in hAligns) or (pos[0] in hAligns and pos[1] not in vAligns) pos[1] = if pos[0] in hAligns then 'm' else 'l' unless @options.autoReposition return pos # if @elem.position().left + @elem.width() + @wrapper.width() < $(window).width() # return 'right' # return 'bottom' throw "Not implemented" getColor: -> styleColors = @getStyle().colors return (styleColors and styleColors[@options.color]) or @options.colors[@options.color] or @options.color getStyle: (name) -> name = @options.style unless name name = 'default' unless name style = styles.user[name] throw "Missing style: #{name}" unless style style updateStyle: -> #update colors @wrapper.find('[data-notify-style]').each (i,e) => $(e).attr 'style', $(e).attr('data-notify-style'). replace(/\{\{\s*color\s*\}\}/ig, @getColor()) # . # replace(/\{\{\s*position\s*\}\}/ig, @getPosition()) #run plugin run: (data, options) -> #update options if $.isPlainObject(options) $.extend @options, options #shortcut special case else if $.type(options) is 'string' @options.color = options if @container and not data @show false #hide return else if not @container and not data return #update content if $.type(data) is 'string' @text.html data.replace('\n', '
') else @text.empty().append(data) @updatePosition() @show true #autohide if @options.autoHide clearTimeout @autohideTimer @autohideTimer = setTimeout => @show false , @options.autoHideDelay destroy: -> @show false, => @wrapper.remove() #when ready $ -> #corner notification container $("body").append cornerElem #auto-detect bootstrap $("link").each -> src = $(@).attr 'href' if src.match /bootstrap/ bootstrapDetected = true return false #add core styles insertCSS styles.core #watch all notifications clicks $(document).on 'click', ".#{className}Wrapper", -> inst = $(@).data className return unless inst if inst.elem inst.show false else inst.destroy() # publicise jquery plugin # return alert "$.#{pluginName} already defined" if $[pluginName]? # $.pluginName( { ... } ) changes options for all instances $[pluginName] = (elem, data, options) -> if (elem and elem.nodeName) or elem.jquery $(elem)[pluginName](data, options) else options = data data = elem new Notification null, data, options elem # publicise methods $[pluginName].options = (options) -> $.extend pluginOptions, options $[pluginName].styles = (s) -> $.extend true, styles.user, s $[pluginName].insertCSS = insertCSS # $( ... ).pluginName( { .. } ) creates a cached instance on each $.fn[pluginName] = (data, options) -> $(@).each -> inst = getAnchorElement($(@)).data(className) if inst inst.run data, options else new Notification $(@), data, options @