├── downloads ├── .gitignore ├── package.json ├── readme.md └── generate.coffee ├── test ├── .gitignore ├── assets │ ├── js │ │ ├── src │ │ ├── downloads │ │ ├── test.coffee │ │ └── tests │ │ │ ├── 210.adapter.native.coffee │ │ │ ├── 220.adapter.ender.coffee │ │ │ ├── 020.opentip.startup.coffee │ │ │ ├── 060.opentip.ajax.coffee │ │ │ ├── 100.utils.coffee │ │ │ ├── 040.opentip.positioning.coffee │ │ │ ├── 110.joint.coffee │ │ │ ├── 050.opentip.draw.coffee │ │ │ ├── 030.opentip.show.coffee │ │ │ ├── 010.opentip.coffee │ │ │ └── 200.adapters.coffee │ └── css │ │ ├── stylus │ │ └── mocha.css ├── server.js ├── package.json ├── views │ ├── index.jade │ └── playground.jade └── app.coffee ├── .gitignore ├── files ├── tests.png ├── explanations.psd └── close-button-angle.png ├── index.js ├── component.json ├── Makefile ├── package.json ├── .tagconfig.json ├── css ├── stylus │ └── opentip.styl └── opentip.css ├── CONTRIBUTING.md ├── src ├── adapter.component.coffee ├── adapter.jquery.coffee ├── adapter.ender.coffee ├── adapter.prototype.coffee └── adapter.native.coffee ├── lib ├── adapter.component.js ├── adapter.jquery.js ├── adapter.ender.js ├── adapter.prototype.js └── adapter.native.js ├── Cakefile ├── docs ├── adapter.prototype.html ├── docco.css └── adapter.jquery.html └── README.md /downloads/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /test/assets/js/src: -------------------------------------------------------------------------------- 1 | ../../../src -------------------------------------------------------------------------------- /test/assets/css/stylus: -------------------------------------------------------------------------------- 1 | ../../../css/stylus -------------------------------------------------------------------------------- /test/assets/js/downloads: -------------------------------------------------------------------------------- 1 | ../../../downloads -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | components 2 | build 3 | .* 4 | !.gitignore 5 | node_modules -------------------------------------------------------------------------------- /files/tests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/variable/opentip/master/files/tests.png -------------------------------------------------------------------------------- /files/explanations.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/variable/opentip/master/files/explanations.psd -------------------------------------------------------------------------------- /test/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 4 | require("coffee-script"); 5 | require("./app"); 6 | -------------------------------------------------------------------------------- /files/close-button-angle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/variable/opentip/master/files/close-button-angle.png -------------------------------------------------------------------------------- /downloads/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opentip-downloads", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "generage": "coffee generate" 7 | }, 8 | "dependencies": { 9 | "request": "*", 10 | "uglify-js": "*", 11 | "uglify-js2": "*" 12 | } 13 | } -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opentip-tests", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app" 7 | }, 8 | "dependencies": { 9 | "express": "3.x", 10 | "jade": "*", 11 | "connect-assets": "2.x", 12 | "coffee-script": "1.x", 13 | "stylus": "0.x", 14 | "nib": "0.x" 15 | } 16 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 Matias Meno 2 | 3 | 4 | // The index.js file for component 5 | var Opentip = require("./lib/opentip.js"); 6 | 7 | 8 | var Adapter = require("./lib/adapter.component.js"); 9 | 10 | // Add the adapter to the list 11 | Opentip.addAdapter(new Adapter()); 12 | 13 | 14 | // Exposing the Opentip class 15 | module.exports = Opentip; -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opentip", 3 | "repo": "enyo/opentip", 4 | "version": "2.2.7", 5 | "description": "Free opensource tooltip class.", 6 | "keywords": [ "tooltip" ], 7 | "dependencies": { 8 | "component/jquery": "*" 9 | }, 10 | "styles": [ "css/opentip.css" ], 11 | "scripts": [ "index.js", "lib/opentip.js", "lib/adapter.component.js" ] 12 | } 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: index.js components 2 | @component build 3 | 4 | rebuild: index.js components 5 | rm -fr build 6 | make build 7 | 8 | components: 9 | @component install 10 | 11 | clean: 12 | rm -fr build components 13 | 14 | downloads: 15 | ./downloads/generate.coffee 16 | 17 | release: 18 | cake build 19 | cake css 20 | make downloads 21 | 22 | all: 23 | clear 24 | make clean 25 | make build 26 | 27 | .PHONY: clean, downloads 28 | -------------------------------------------------------------------------------- /downloads/readme.md: -------------------------------------------------------------------------------- 1 | # Downloads 2 | 3 | Choose the **one** file that suits your needs. If you don't intend to support IE8 4 | (or include excanvas yourself) you don't need the `-excanvas` version. 5 | 6 | You'll probably want the `.min.js` version unless you count on debuggin Opentip. 7 | 8 | ## Generate downloads 9 | 10 | To generate downloads you need to run `npm install` once from within the 11 | downloads folder, and then `make downloads`. -------------------------------------------------------------------------------- /test/assets/js/test.coffee: -------------------------------------------------------------------------------- 1 | #= require_tree tests 2 | 3 | 4 | 5 | window.Test = 6 | triggerEvent: (element, event) -> 7 | if document.createEvent? 8 | # dispatch for firefox + others 9 | evt = document.createEvent "HTMLEvents" 10 | evt.initEvent event, true, true # event type,bubbling,cancelable 11 | element.dispatchEvent evt 12 | else 13 | # dispatch for IE 14 | evt = document.createEventObject() 15 | element.fireEvent "on#{event}", evt 16 | 17 | clickElement: (element) -> 18 | @triggerEvent element, "click" 19 | -------------------------------------------------------------------------------- /test/assets/js/tests/210.adapter.native.coffee: -------------------------------------------------------------------------------- 1 | 2 | 3 | unless Opentip.adapters.component? 4 | # Only test this adapter if not testing component 5 | describe "Native adapter", -> 6 | adapter = Opentip.adapter 7 | 8 | describe "DOM", -> 9 | describe "create()", -> 10 | it "should properly create DOM elements from string", -> 11 | elements = adapter.create """
HI
""" 12 | expect(elements).to.be.an "object" 13 | expect(elements.length).to.equal 1 14 | expect(elements[0].className).to.equal "test" 15 | it "the created elements should be wrapped", -> 16 | elements = adapter.create """
HI
""" 17 | wrapped = adapter.wrap elements 18 | expect(elements).to.equal wrapped 19 | 20 | describe "wrap()", -> 21 | it "should wrap the element in an array", -> 22 | element = document.createElement "div" 23 | wrapped = adapter.wrap element 24 | expect(element).to.equal wrapped[0] 25 | 26 | it "should properly wrap nodelists", -> 27 | wrapped = adapter.wrap document.body.childNodes 28 | expect(wrapped).to.not.be.a NodeList 29 | -------------------------------------------------------------------------------- /test/views/index.jade: -------------------------------------------------------------------------------- 1 | !!! 2 | head 3 | meta(charset="utf-8") 4 | 5 | title Opentip | Tests 6 | 7 | meta(http-equiv="X-UA-Compatible", content="IE=edge") 8 | 9 | != css('mocha.css') 10 | 11 | | 14 | 15 | 16 | != js('lib/jquery-1.8.0') 17 | != js('lib/prototype-1.7.1') 18 | :coffeescript 19 | jQuery.noConflict() 20 | 21 | != js('lib/expect') 22 | != js('lib/mocha') 23 | :coffeescript 24 | mocha.setup "bdd" 25 | 26 | != js('lib/sinon-1.4.2') 27 | 28 | 29 | - if (typeof component != "undefined") 30 | != js('build/build.js') 31 | 32 | :coffeescript 33 | window.Opentip = require "opentip" 34 | 35 | - else 36 | != js('src/opentip') 37 | 38 | != js('lib/ender') 39 | :coffeescript 40 | ender.noConflict() 41 | 42 | != js('src/adapter.native') // Should stay first so it's the default adapter 43 | != js('src/adapter.ender') 44 | != js('src/adapter.jquery') 45 | != js('src/adapter.prototype') 46 | 47 | != js('test') 48 | 49 | :coffeescript 50 | globals = [ "stats", "report", "opentip-*", "jQuery*" ] 51 | jQuery -> mocha.globals(globals).run() 52 | 53 | body 54 | 55 | 56 | 57 | #mocha 58 | -------------------------------------------------------------------------------- /test/app.coffee: -------------------------------------------------------------------------------- 1 | 2 | # Module dependencies. 3 | express = require "express" 4 | http = require "http" 5 | path = require "path" 6 | app = express() 7 | 8 | app.configure -> 9 | app.set "port", process.env.PORT or 3000 10 | app.set "views", __dirname + "/views" 11 | app.set "view engine", "jade" 12 | app.use express.favicon() 13 | app.use express.logger("dev") 14 | app.use express.bodyParser() 15 | app.use express.methodOverride() 16 | app.use app.router 17 | 18 | # app.use(express.static(path.join(__dirname, 'public'))); 19 | app.use require("connect-assets") 20 | src: "#{__dirname}/assets" 21 | buildDir: "#{__dirname}/cache/assets" 22 | 23 | app.configure "development", -> 24 | app.use express.errorHandler() 25 | 26 | 27 | app.get "/", (req, res) -> res.render "index" 28 | app.get "/component", (req, res) -> res.render "index", component: yes 29 | app.get "/playground", (req, res) -> res.render "playground" 30 | app.get "/ajax-test", (req, res) -> res.send "success get" 31 | app.post "/ajax-test", (req, res) -> res.send "success post" 32 | app.get "/ajax-test-delayed", (req, res) -> setTimeout (-> res.send "success get"), 500 33 | 34 | http.createServer(app).listen app.get("port"), -> console.log "Express server listening on port #{app.get "port"}" 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opentip", 3 | "version": "2.2.7", 4 | "description": "Free opensource tooltip class.", 5 | "keywords": [ "tooltip" ], 6 | "homepage": "http://www.opentip.org", 7 | "main": [ 8 | "./lib/opentip.js", 9 | "./lib/adapter.ender.js" 10 | ], 11 | "maintainers": [ 12 | { 13 | "name": "Matias Meno", 14 | "email": "m@tias.me", 15 | "web": "http://www.matiasmeno.com" 16 | } 17 | ], 18 | "contributors": [ 19 | { 20 | "name": "Matias Meno", 21 | "email": "m@tias.me", 22 | "web": "http://www.matiasmeno.com" 23 | } 24 | ], 25 | "bugs": { 26 | "mail": "m@tias.me" 27 | }, 28 | "licenses": [ 29 | { 30 | "type": "MIT", 31 | "url": "http://www.opensource.org/licenses/MIT" 32 | } 33 | ], 34 | "repositories": [ 35 | { 36 | "type": "git", 37 | "url": "https://github.com/enyo/opentip.git" 38 | } 39 | ], 40 | "dependencies": { 41 | "qwery": "3.x", 42 | "domready": "0.x", 43 | "bean": "0.x", 44 | "bonzo": "1.x", 45 | "reqwest": "0.x" 46 | }, 47 | "devDependencies": { 48 | "coffee-script": "*", 49 | "stylus": "*", 50 | "uglify-js2": "*", 51 | "request": "*", 52 | "nib": "*" 53 | }, 54 | "ender": "noop" 55 | } 56 | -------------------------------------------------------------------------------- /test/assets/js/tests/220.adapter.ender.coffee: -------------------------------------------------------------------------------- 1 | 2 | unless Opentip.adapters.component? 3 | # Only test this adapter if not testing component 4 | $ = jQuery 5 | 6 | describe "Ender adapter", -> 7 | adapter = Opentip.adapter 8 | 9 | describe "DOM", -> 10 | describe "create()", -> 11 | it "should properly create DOM elements from string", -> 12 | elements = adapter.create """
HI
""" 13 | expect(elements).to.be.an "object" 14 | expect(elements.length).to.equal 1 15 | expect(elements[0].className).to.equal "test" 16 | 17 | describe "wrap()", -> 18 | it "should return a bonzo element", -> 19 | element = document.createElement "div" 20 | wrapped = adapter.wrap element 21 | expect(element).to.not.equal wrapped 22 | expect(element).to.equal adapter.unwrap wrapped 23 | 24 | 25 | describe "tagName()", -> 26 | it "should return the tagName of passed ender element", -> 27 | element = $("div")[0] 28 | expect(adapter.tagName element).to.equal "DIV" 29 | 30 | describe "attr()", -> 31 | it "should return the attribute of passed ender element", -> 32 | element = $("""""")[0] 33 | expect(adapter.attr element, "href").to.equal "http://link" 34 | 35 | describe "observe()", -> 36 | it "should observe given event on ender element", (done) -> 37 | element = $("link")[0] 38 | adapter.observe element, "click", -> done() 39 | Test.clickElement element 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /test/assets/js/tests/020.opentip.startup.coffee: -------------------------------------------------------------------------------- 1 | 2 | $ = jQuery 3 | 4 | describe "Opentip - Startup", -> 5 | adapter = Opentip.adapter 6 | opentip = null 7 | 8 | afterEach -> 9 | opentip[prop]?.restore?() for own prop of opentip 10 | opentip?.deactivate?() 11 | $("[data-ot]").remove() 12 | $(".opentip-container").remove() 13 | 14 | it "should find all elements with data-ot()", -> 15 | trigger = $("""
""")[0] 16 | $(document.body).append trigger 17 | Opentip.findElements() 18 | expect(adapter.data(trigger, "opentips")).to.be.an Array 19 | expect(adapter.data(trigger, "opentips").length).to.equal 1 20 | expect(adapter.data(trigger, "opentips")[0]).to.be.an Opentip 21 | 22 | it "should take configuration from data- attributes", -> 23 | trigger = $("""
""")[0] 24 | $(document.body).append trigger 25 | Opentip.findElements() 26 | expect(adapter.data(trigger, "opentips")[0]).to.be.an Opentip 27 | opentip = adapter.data(trigger, "opentips")[0] 28 | expect(opentip.options.hideTrigger).to.eql "closeButton" 29 | expect(opentip.options.showOn).to.eql "click" 30 | 31 | it "should properly parse boolean data- attributes", -> 32 | trigger = $("""
""")[0] 33 | $(document.body).append trigger 34 | Opentip.findElements() 35 | opentip = adapter.data(trigger, "opentips")[0] 36 | expect(opentip.options.shadow).to.be yes 37 | expect(opentip.options.autoOffset).to.be no 38 | expect(opentip.options.containInViewport).to.be no 39 | 40 | -------------------------------------------------------------------------------- /downloads/generate.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | 3 | fs = require "fs" 4 | request = require "request" 5 | 6 | uglifyJs2 = require "uglify-js2" 7 | 8 | 9 | downloads = 10 | jquery: [ 11 | "lib/opentip.js" 12 | "lib/adapter.jquery.js" 13 | ] 14 | prototype: [ 15 | "lib/opentip.js" 16 | "lib/adapter.prototype.js" 17 | ] 18 | native: [ 19 | "lib/opentip.js" 20 | "lib/adapter.native.js" 21 | ] 22 | 23 | 24 | header = """ 25 | // Opentip v2.2.7 26 | // Copyright (c) 2009-2012 27 | // www.opentip.org 28 | // MIT Licensed 29 | 30 | """ 31 | 32 | 33 | 34 | # First download excanvas 35 | console.log "Downloading excanvas" 36 | 37 | request "https://raw.github.com/enyo/excanvas/master/index.js", (error, response, excanvas) -> 38 | return console.error error unless !error and response.statusCode == 200 39 | 40 | console.log "Downloading classList" 41 | request "https://raw.github.com/eligrey/classList.js/master/classList.js", (error, response, classList) -> 42 | return console.error error unless !error and response.statusCode == 200 43 | 44 | console.log "Downloading addEventListener polyfill" 45 | # request "https://gist.github.com/raw/4684074/e98964ff5aec0032cab344bd40c4f528dec7ac78/addEventListener-polyfill.js", (error, response, addEventListener) -> 46 | request "https://gist.github.com/raw/4684216/c58a272ef9d9e0f55ea5e90ac313e3a3b2f2b7b3/eventListener.polyfill.js", (error, response, addEventListener) -> 47 | return console.error error unless !error and response.statusCode == 200 48 | 49 | 50 | saveFile = (originalDownloadName, contents, withExcanvas) -> 51 | downloadName = originalDownloadName 52 | 53 | console.log "Minfiying and saving#{if withExcanvas then " with excanvas" else ""}..." 54 | 55 | if withExcanvas 56 | contents += "\n\n" + excanvas 57 | downloadName += "-excanvas" 58 | 59 | if originalDownloadName == "native" 60 | contents += "\n\n" + classList 61 | contents += "\n\n" + addEventListener 62 | 63 | targetFile = "#{__dirname}/opentip-#{downloadName}.js" 64 | 65 | fs.writeFileSync targetFile, contents, "utf-8" 66 | 67 | mergedMinified = header + uglifyJs2.minify(targetFile).code 68 | targetFile = "#{__dirname}/opentip-#{downloadName}.min.js" 69 | fs.writeFileSync targetFile, mergedMinified, "utf-8" 70 | 71 | 72 | for downloadName, files of downloads 73 | merged = [] 74 | console.log "" 75 | console.log "Processing '#{downloadName}.js'" 76 | for file in files 77 | console.log "Adding '#{file}'." 78 | merged.push fs.readFileSync "#{__dirname}/../#{file}", "utf-8" 79 | 80 | merged = merged.join "\n\n" 81 | 82 | saveFile downloadName, merged 83 | saveFile downloadName, merged, yes 84 | 85 | console.log "Done" 86 | 87 | console.log "" 88 | console.log "" 89 | -------------------------------------------------------------------------------- /.tagconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | { 4 | "name": "README.md", 5 | "regexs": [ 6 | "Version ###" 7 | ] 8 | }, 9 | { 10 | "name": "src/opentip.coffee", 11 | "regexs": [ 12 | "Opentip v###", 13 | "Opentip\\.version = \"###\"" 14 | ] 15 | }, 16 | { 17 | "name": "lib/opentip.js", 18 | "regexs": [ 19 | "Opentip v###", 20 | "Opentip\\.version = \"###\"" 21 | ] 22 | }, 23 | { 24 | "name": "component.json", 25 | "regexs": [ 26 | "\"version\": \"###\"" 27 | ] 28 | }, 29 | { 30 | "name": "package.json", 31 | "regexs": [ 32 | "\"version\": \"###\"" 33 | ] 34 | }, 35 | { 36 | "name": "downloads/generate.coffee", 37 | "regexs": [ 38 | "Opentip v###" 39 | ] 40 | }, 41 | 42 | 43 | { 44 | "name": "downloads/opentip-jquery-excanvas.js", 45 | "regexs": [ 46 | "Opentip\\.version = \"###\"", 47 | "Opentip v###" 48 | ] 49 | }, 50 | { 51 | "name": "downloads/opentip-jquery.js", 52 | "regexs": [ 53 | "Opentip\\.version = \"###\"", 54 | "Opentip v###" 55 | ] 56 | }, 57 | { 58 | "name": "downloads/opentip-native-excanvas.js", 59 | "regexs": [ 60 | "Opentip\\.version = \"###\"", 61 | "Opentip v###" 62 | ] 63 | }, 64 | { 65 | "name": "downloads/opentip-native.js", 66 | "regexs": [ 67 | "Opentip\\.version = \"###\"", 68 | "Opentip v###" 69 | ] 70 | }, 71 | { 72 | "name": "downloads/opentip-prototype-excanvas.js", 73 | "regexs": [ 74 | "Opentip\\.version = \"###\"", 75 | "Opentip v###" 76 | ] 77 | }, 78 | { 79 | "name": "downloads/opentip-prototype.js", 80 | "regexs": [ 81 | "Opentip\\.version = \"###\"", 82 | "Opentip v###" 83 | ] 84 | }, 85 | 86 | { 87 | "name": "downloads/opentip-jquery-excanvas.min.js", 88 | "regexs": [ 89 | "Opentip\\.version=\"###\"", 90 | "Opentip v###" 91 | ] 92 | }, 93 | { 94 | "name": "downloads/opentip-jquery.min.js", 95 | "regexs": [ 96 | "Opentip\\.version=\"###\"", 97 | "Opentip v###" 98 | ] 99 | }, 100 | { 101 | "name": "downloads/opentip-native-excanvas.min.js", 102 | "regexs": [ 103 | "Opentip\\.version=\"###\"", 104 | "Opentip v###" 105 | ] 106 | }, 107 | { 108 | "name": "downloads/opentip-native.min.js", 109 | "regexs": [ 110 | "Opentip\\.version=\"###\"", 111 | "Opentip v###" 112 | ] 113 | }, 114 | { 115 | "name": "downloads/opentip-prototype-excanvas.min.js", 116 | "regexs": [ 117 | "Opentip\\.version=\"###\"", 118 | "Opentip v###" 119 | ] 120 | }, 121 | { 122 | "name": "downloads/opentip-prototype.min.js", 123 | "regexs": [ 124 | "Opentip\\.version=\"###\"", 125 | "Opentip v###" 126 | ] 127 | } 128 | 129 | ] 130 | } 131 | -------------------------------------------------------------------------------- /css/stylus/opentip.styl: -------------------------------------------------------------------------------- 1 | @import "nib" 2 | 3 | 4 | @keyframes loading 5 | from 6 | transform rotate(0deg) 7 | to 8 | transform rotate(360deg) 9 | 10 | 11 | .opentip-container, .opentip-container * 12 | box-sizing border-box 13 | 14 | .opentip-container 15 | position absolute 16 | max-width 300px 17 | z-index 100 18 | transition transform 1s ease-in-out 19 | pointer-events none 20 | transform translateX(0) translateY(0) 21 | 22 | 23 | 24 | &.fixed 25 | &.hidden 26 | &.going-to-show 27 | &.hiding 28 | &.stem-top.stem-center 29 | transform translateY(-5px) 30 | &.stem-top.stem-right 31 | transform translateY(-5px) translateX(5px) 32 | &.stem-middle.stem-right 33 | transform translateX(5px) 34 | &.stem-bottom.stem-right 35 | transform translateY(5px) translateX(5px) 36 | &.stem-bottom.stem-center 37 | transform translateY(5px) 38 | &.stem-bottom.stem-left 39 | transform translateY(5px) translateX(-5px) 40 | &.stem-middle.stem-left 41 | transform translateX(-5px) 42 | &.stem-top.stem-left 43 | transform translateY(-5px) translateX(-5px) 44 | 45 | &.fixed 46 | // When it's fixed, the close button, and links inside the tooltip should 47 | // be clickable 48 | .opentip 49 | pointer-events auto 50 | 51 | &.hidden 52 | display none 53 | 54 | .opentip 55 | position relative 56 | font-size 13px 57 | line-height 120% 58 | padding 9px 14px 59 | color #4F4B47 60 | text-shadow -1px -1px 0px rgba(255, 255, 255, 0.2) 61 | 62 | .header 63 | margin 0 64 | padding 0 65 | 66 | .close 67 | pointer-events auto 68 | display block 69 | absolute top -12px left 60px 70 | color rgba(0, 0, 0, 0.5) 71 | background rgba(0, 0, 0, 0) // So IE9 renders this as clickable 72 | text-decoration none 73 | 74 | span 75 | display none 76 | 77 | .loading-indicator 78 | display none 79 | 80 | &.loading 81 | .loading-indicator 82 | width 30px 83 | height @width 84 | font-size @width 85 | line-height @width 86 | font-weight bold 87 | display block 88 | span 89 | display block 90 | animation loading 2s linear infinite 91 | text-align center 92 | 93 | 94 | 95 | // Styles 96 | // ====== 97 | &.style-dark 98 | &.style-alert 99 | .opentip 100 | color #f8f8f8 101 | text-shadow 1px 1px 0px rgba(0, 0, 0, 0.2) 102 | 103 | &.style-glass 104 | .opentip 105 | padding 15px 25px 106 | color #317CC5 107 | text-shadow 1px 1px 8px rgba(0, 94, 153, 0.3) 108 | 109 | 110 | // Effects 111 | // ======= 112 | &.hide-effect-fade 113 | transition transform 0.5s ease-in-out, opacity 1s ease-in-out 114 | opacity 1 115 | &.hiding 116 | opacity 0 117 | 118 | &.show-effect-appear 119 | &.going-to-show 120 | &.showing 121 | transition transform 0.5s ease-in-out, opacity 1s ease-in-out 122 | &.going-to-show 123 | opacity 0 124 | &.showing 125 | opacity 1 126 | &.visible 127 | opacity 1 128 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contribute 2 | ========== 3 | 4 | The latest stable version is always in the **[master](https://github.com/enyo/opentip)** branch (which always 5 | points at the latest version tag). 6 | 7 | The latest development version is in the **[develop](https://github.com/enyo/opentip/tree/develop)** branch. 8 | 9 | > Use the develop branch if you want to contribute or test features. 10 | 11 | Please do also **send pull requests to the `develop` branch**. 12 | I will **not** merge pull requests to the `master` branch. 13 | 14 | 15 | Make sure that changes pass all [tests](#testing). 16 | 17 | 18 | ### Coffeescript & Stylus (-> Javascript & CSS) 19 | 20 | Opentip is written in [Coffeescript](http://coffeescript.org) and 21 | [Stylus](http://learnboost.github.com/stylus/) so *do not* make 22 | changes to the Javascript or CSS files 23 | 24 | **I will not merge requests written in Javascript or CSS.** 25 | 26 | Getting started 27 | --------------- 28 | 29 | You need node to compile and test Opentip. So [install node](http://nodejs.org) 30 | first if you haven't done so already. 31 | 32 | 33 | ### Building Opentip 34 | 35 | 36 | First you have to setup the node modules to build Opentip. Simply run this in 37 | the Opentip directory: 38 | 39 | ```bash 40 | $ npm install 41 | ``` 42 | 43 | This will setup Coffeescript, Stylus and a few other dependencies to build and 44 | bundle the library. 45 | 46 | To compile (build) and bundle the library use `cake`. 47 | 48 | Just type the command without any arguments `$ cake` in the source directory to 49 | list all commands available. It will look something like this: 50 | 51 | ```bash 52 | cake docs # generate documentation 53 | cake build # compile source 54 | cake watch # compile and watch 55 | cake css # compile stylus 56 | cake watchcss # compile and watch stylus 57 | ``` 58 | 59 | To compile all source files: 60 | 61 | ```bash 62 | $ cd path/to/opentip-source 63 | $ cake build 64 | ``` 65 | 66 | > I prefer pull requests that only changed `.coffee` and `.stylus` files, since 67 | > I only checkin the `.css` and `.js` files before a release. But I accept 68 | > pull requests that contain the compiled files as well. 69 | 70 | 71 | ### Testing 72 | 73 | Go into the `test/` directory and install all dependencies. (You only have 74 | to do this the first time): 75 | 76 | ```bash 77 | $ cd test/ 78 | $ npm install 79 | ``` 80 | 81 | And you're ready to launch the server: 82 | 83 | ```bash 84 | $ ./server.js 85 | ``` 86 | 87 | Now simply visit `http://localhost:3000` in your browser to see the tests. 88 | 89 | It should look like this: 90 | 91 | ![Tests screenshot](https://raw.github.com/enyo/opentip/develop/files/tests.png) 92 | 93 | All tests are located in `assets/js/tests/` and are written in coffeescript but 94 | compiled on the fly. 95 | 96 | The webserver also automatically compiles any opentip changes (as well as the 97 | adapter changes), so don't worry about compiling coffeescript. When the time 98 | comes to deploy everything, I'll take care of properly bundling all Javascript 99 | files. 100 | 101 | If you add a change, please make sure that all tests pass! 102 | 103 | -------------------------------------------------------------------------------- /test/assets/js/tests/060.opentip.ajax.coffee: -------------------------------------------------------------------------------- 1 | 2 | $ = jQuery 3 | 4 | describe "Opentip - AJAX", -> 5 | adapter = Opentip.adapter 6 | opentip = null 7 | triggerElementExists = yes 8 | 9 | beforeEach -> 10 | triggerElementExists = yes 11 | 12 | afterEach -> 13 | opentip[prop]?.restore?() for own prop of opentip 14 | opentip.deactivate() 15 | $(".opentip-container").remove() 16 | 17 | describe "_loadAjax()", -> 18 | describe "on success", -> 19 | beforeEach -> 20 | sinon.stub adapter, "ajax", (options) -> 21 | options.onSuccess "response text" 22 | options.onComplete() 23 | afterEach -> 24 | adapter.ajax.restore() 25 | 26 | it "should use adapter.ajax", -> 27 | opentip = new Opentip adapter.create("
"), "Test", ajax: "http://www.test.com", ajaxMethod: "post" 28 | opentip._loadAjax() 29 | expect(adapter.ajax.callCount).to.be 1 30 | expect(adapter.ajax.args[0][0].url).to.equal "http://www.test.com" 31 | expect(adapter.ajax.args[0][0].method).to.equal "post" 32 | 33 | 34 | it "should be called by show() and update the content (only once!)", -> 35 | opentip = new Opentip adapter.create("
"), "Test", ajax: "http://www.test.com", ajaxMethod: "post", ajaxCache: yes 36 | sinon.stub opentip, "_triggerElementExists", -> yes 37 | 38 | sinon.spy opentip, "setContent"#, (content) -> #expect(content).to.be "response text" 39 | 40 | opentip.show() 41 | opentip.hide() 42 | opentip.show() 43 | opentip.hide() 44 | opentip.show() 45 | opentip.hide() 46 | expect(adapter.ajax.callCount).to.be 1 47 | expect(opentip.setContent.callCount).to.be 2 # Every time AJAX gets loaded, it empties the content 48 | expect(opentip.content).to.be "response text" 49 | 50 | 51 | it "if ajaxCache: false, should be called by show() and update the content every time show is called", -> 52 | opentip = new Opentip adapter.create("
"), "Test", ajax: "http://www.test.com", ajaxMethod: "post", ajaxCache: no 53 | sinon.stub opentip, "_triggerElementExists", -> yes 54 | sinon.spy opentip, "setContent"#, (content) -> expect(content).to.be "response text" 55 | opentip.show() 56 | opentip.hide() 57 | opentip.show() 58 | opentip.hide() 59 | opentip.show() 60 | opentip.hide() 61 | expect(adapter.ajax.callCount).to.be 3 62 | expect(opentip.setContent.callCount).to.be 6 # Every time AJAX gets loaded, it empties the content 63 | 64 | describe "on error", -> 65 | beforeEach -> 66 | sinon.stub adapter, "ajax", (options) -> 67 | options.onError "Some error" 68 | afterEach -> 69 | adapter.ajax.restore() 70 | 71 | it "should use the options.ajaxErrorMessage on failure", -> 72 | opentip = new Opentip adapter.create("
"), "Test", ajax: "http://www.test.com", ajaxMethod: "post", ajaxErrorMessage: "No download dude." 73 | 74 | expect(opentip.options.ajaxErrorMessage).to.be "No download dude." 75 | 76 | expect(opentip.content).to.be "Test" 77 | 78 | opentip._loadAjax() 79 | 80 | expect(opentip.content).to.be opentip.options.ajaxErrorMessage -------------------------------------------------------------------------------- /test/assets/js/tests/100.utils.coffee: -------------------------------------------------------------------------------- 1 | 2 | describe "utils", -> 3 | adapter = Opentip.adapter 4 | 5 | describe "debug()", -> 6 | consoleDebug = console.debug 7 | beforeEach -> sinon.stub console, "debug" 8 | afterEach -> console.debug.restore() 9 | 10 | it "should only debug when Opentip.debug == true", -> 11 | Opentip.debug = off 12 | Opentip::debug "test" 13 | expect(console.debug.called).to.not.be.ok() 14 | Opentip.debug = on 15 | Opentip::debug "test" 16 | expect(console.debug.called).to.be.ok() 17 | 18 | it "should include the opentip id", -> 19 | Opentip.debug = on 20 | opentip = new Opentip document.createElement("span") 21 | # Automatically calls debug but to make sure: 22 | opentip.debug "test" 23 | expect(console.debug.getCall(0).args[0]).to.be "##{opentip.id} |" 24 | 25 | describe "ucfirst()", -> 26 | it "should transform the first character to uppercase", -> 27 | expect(Opentip::ucfirst "abc def").to.equal "Abc def" 28 | 29 | describe "dasherize()", -> 30 | it "should transform camelized words into dasherized", -> 31 | expect(Opentip::dasherize "testAbcHoiTEST").to.equal "test-abc-hoi-t-e-s-t" 32 | 33 | describe "_positionsEqual()", -> 34 | it "should properly compare positions", -> 35 | eq = Opentip::_positionsEqual 36 | expect(eq { left: 0, top: 0 }, { left: 0, top: 0 }).to.be.ok() 37 | expect(eq { left: 100, top: 20 }, { left: 100, top: 20 }).to.be.ok() 38 | expect(eq { left: 100, top: 20 }, { left: 101, top: 20 }).to.not.be.ok() 39 | expect(eq null, { left: 101, top: 20 }).to.not.be.ok() 40 | expect(eq null, null).to.not.be.ok() 41 | 42 | describe "_dimensionsEqual()", -> 43 | it "should properly compare dimensions", -> 44 | eq = Opentip::_dimensionsEqual 45 | expect(eq { width: 0, height: 0 }, { width: 0, height: 0 }).to.be.ok() 46 | expect(eq { width: 100, height: 20 }, { width: 100, height: 20 }).to.be.ok() 47 | expect(eq { width: 100, height: 20 }, { width: 101, height: 20 }).to.not.be.ok() 48 | expect(eq null, { width: 101, height: 20 }).to.not.be.ok() 49 | expect(eq null, null).to.not.be.ok() 50 | 51 | describe "setCss3Style()", -> 52 | opentip = new Opentip adapter.create("
"), "Test" 53 | it "should set the style for all vendors", -> 54 | element = document.createElement "div" 55 | opentip.setCss3Style element, { opacity: "0.5", transitionDuration: "1s" } 56 | transitionDuration = false 57 | opacity = false 58 | 59 | for vendor in [ 60 | "" 61 | "Khtml" 62 | "Ms" 63 | "O" 64 | "Moz" 65 | "Webkit" 66 | ] 67 | prop = if vendor then "#{vendor}TransitionDuration" else "transitionDuration" 68 | transitionDuration = true if element.style[prop] == "1s" 69 | prop = if vendor then "#{vendor}Opacity" else "opacity" 70 | opacity = true if element.style[prop] == "0.5" 71 | # expect(element.style["-moz-transition-duration"]).to.be "1s" 72 | # expect(element.style["-moz-opacity"]).to.be "0.5" 73 | expect(transitionDuration).to.be true 74 | expect(opacity).to.be true 75 | # expect(element.style["-o-transition-duration"]).to.be "1s" 76 | 77 | describe "defer()", -> 78 | it "should call the callback as soon as possible" 79 | 80 | describe "setTimeout()", -> 81 | it "should wrap window.setTimeout but with seconds" 82 | 83 | -------------------------------------------------------------------------------- /test/assets/js/tests/040.opentip.positioning.coffee: -------------------------------------------------------------------------------- 1 | 2 | $ = jQuery 3 | 4 | describe "Opentip - Positioning", -> 5 | adapter = Opentip.adapter 6 | opentip = null 7 | triggerElementExists = yes 8 | 9 | beforeEach -> 10 | triggerElementExists = yes 11 | 12 | afterEach -> 13 | opentip[prop].restore?() for prop of opentip 14 | $(".opentip-container").remove() 15 | 16 | describe "fixed", -> 17 | element = adapter.create("""
""") 18 | beforeEach -> 19 | adapter.append document.body, element 20 | afterEach -> 21 | $(adapter.unwrap element).remove() 22 | 23 | describe "without autoOffset", -> 24 | it "should correctly position opentip without border and stem", -> 25 | opentip = new Opentip element, "Test", delay: 0, target: yes, borderWidth: 0, stem: off, offset: [ 0, 0 ], autoOffset: false 26 | elementOffset = adapter.offset element 27 | expect(elementOffset).to.eql left: 500, top: 500 28 | opentip.reposition() 29 | expect(opentip.currentPosition).to.eql left: 550, top: 550 30 | it "should correctly position opentip with", -> 31 | opentip = new Opentip element, "Test", delay: 0, target: yes, borderWidth: 10, stem: off, offset: [ 0, 0 ], autoOffset: false 32 | elementOffset = adapter.offset element 33 | opentip.reposition() 34 | expect(opentip.currentPosition).to.eql left: 560, top: 560 35 | it "should correctly position opentip with stem on the left", -> 36 | opentip = new Opentip element, "Test", delay: 0, target: yes, borderWidth: 0, stem: yes, tipJoint: "top left", stemLength: 5, offset: [ 0, 0 ], autoOffset: false 37 | elementOffset = adapter.offset element 38 | opentip.reposition() 39 | expect(opentip.currentPosition).to.eql left: 550, top: 550 40 | it "should correctly position opentip on the bottom right", -> 41 | opentip = new Opentip element, "Test", delay: 0, target: yes, borderWidth: 0, stem: off, tipJoint: "bottom right", offset: [ 0, 0 ], autoOffset: false 42 | opentip.dimensions = width: 200, height: 200 43 | elementOffset = adapter.offset element 44 | elementDimensions = adapter.dimensions element 45 | expect(elementDimensions).to.eql width: 50, height: 50 46 | opentip.reposition() 47 | expect(opentip.currentPosition).to.eql left: 300, top: 300 48 | it "should correctly position opentip on the bottom right with stem", -> 49 | opentip = new Opentip element, "Test", delay: 0, target: yes, borderWidth: 0, stem: yes, tipJoint: "bottom right", stemLength: 10, offset: [ 0, 0 ], autoOffset: false 50 | opentip.dimensions = width: 200, height: 200 51 | elementOffset = adapter.offset element 52 | elementDimensions = adapter.dimensions element 53 | expect(elementDimensions).to.eql width: 50, height: 50 54 | opentip.reposition() 55 | expect(opentip.currentPosition).to.eql left: 300, top: 300 # The autoOffset takes care of accounting for the stem 56 | 57 | 58 | describe "following mouse", -> 59 | it "should correctly position opentip when following mouse" 60 | 61 | describe "_ensureViewportContainment()", -> 62 | it "should put the tooltip on the other side when it's sticking out" 63 | it "shouldn't do anything if the viewport is smaller than the tooltip" 64 | it "should revert if the tooltip sticks out the other side as well" 65 | 66 | -------------------------------------------------------------------------------- /test/assets/css/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | body { 3 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 4 | padding: 60px 50px; 5 | } 6 | 7 | #mocha ul, #mocha li { 8 | margin: 0; 9 | padding: 0; 10 | } 11 | 12 | #mocha ul { 13 | list-style: none; 14 | } 15 | 16 | #mocha h1, #mocha h2 { 17 | margin: 0; 18 | } 19 | 20 | #mocha h1 { 21 | margin-top: 15px; 22 | font-size: 1em; 23 | font-weight: 200; 24 | } 25 | 26 | #mocha h1 a { 27 | text-decoration: none; 28 | color: inherit; 29 | } 30 | 31 | #mocha h1 a:hover { 32 | text-decoration: underline; 33 | } 34 | 35 | #mocha .suite .suite h1 { 36 | margin-top: 0; 37 | font-size: .8em; 38 | } 39 | 40 | #mocha h2 { 41 | font-size: 12px; 42 | font-weight: normal; 43 | cursor: pointer; 44 | } 45 | 46 | #mocha .suite { 47 | margin-left: 15px; 48 | } 49 | 50 | #mocha .test { 51 | margin-left: 15px; 52 | } 53 | 54 | #mocha .test:hover h2::after { 55 | position: relative; 56 | top: 0; 57 | right: -10px; 58 | content: '(view source)'; 59 | font-size: 12px; 60 | font-family: arial; 61 | color: #888; 62 | } 63 | 64 | #mocha .test.pending:hover h2::after { 65 | content: '(pending)'; 66 | font-family: arial; 67 | } 68 | 69 | #mocha .test.pass.medium .duration { 70 | background: #C09853; 71 | } 72 | 73 | #mocha .test.pass.slow .duration { 74 | background: #B94A48; 75 | } 76 | 77 | #mocha .test.pass::before { 78 | content: '✓'; 79 | font-size: 12px; 80 | display: block; 81 | float: left; 82 | margin-right: 5px; 83 | color: #00d6b2; 84 | } 85 | 86 | #mocha .test.pass .duration { 87 | font-size: 9px; 88 | margin-left: 5px; 89 | padding: 2px 5px; 90 | color: white; 91 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 92 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 93 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 94 | -webkit-border-radius: 5px; 95 | -moz-border-radius: 5px; 96 | -ms-border-radius: 5px; 97 | -o-border-radius: 5px; 98 | border-radius: 5px; 99 | } 100 | 101 | #mocha .test.pass.fast .duration { 102 | display: none; 103 | } 104 | 105 | #mocha .test.pending { 106 | color: #0b97c4; 107 | } 108 | 109 | #mocha .test.pending::before { 110 | content: '◦'; 111 | color: #0b97c4; 112 | } 113 | 114 | #mocha .test.fail { 115 | color: #c00; 116 | } 117 | 118 | #mocha .test.fail pre { 119 | color: black; 120 | } 121 | 122 | #mocha .test.fail::before { 123 | content: '✖'; 124 | font-size: 12px; 125 | display: block; 126 | float: left; 127 | margin-right: 5px; 128 | color: #c00; 129 | } 130 | 131 | #mocha .test pre.error { 132 | color: #c00; 133 | } 134 | 135 | #mocha .test pre { 136 | display: inline-block; 137 | font: 12px/1.5 monaco, monospace; 138 | margin: 5px; 139 | padding: 15px; 140 | border: 1px solid #eee; 141 | border-bottom-color: #ddd; 142 | -webkit-border-radius: 3px; 143 | -webkit-box-shadow: 0 1px 3px #eee; 144 | } 145 | 146 | #report.pass .test.fail { 147 | display: none; 148 | } 149 | 150 | #report.fail .test.pass { 151 | display: none; 152 | } 153 | 154 | #error { 155 | color: #c00; 156 | font-size: 1.5 em; 157 | font-weight: 100; 158 | letter-spacing: 1px; 159 | } 160 | 161 | #stats { 162 | position: fixed; 163 | top: 15px; 164 | right: 10px; 165 | font-size: 12px; 166 | margin: 0; 167 | color: #888; 168 | } 169 | 170 | #stats .progress { 171 | float: right; 172 | padding-top: 0; 173 | } 174 | 175 | #stats em { 176 | color: black; 177 | } 178 | 179 | #stats a { 180 | text-decoration: none; 181 | color: inherit; 182 | } 183 | 184 | #stats a:hover { 185 | border-bottom: 1px solid #eee; 186 | } 187 | 188 | #stats li { 189 | display: inline-block; 190 | margin: 0 5px; 191 | list-style: none; 192 | padding-top: 11px; 193 | } 194 | 195 | code .comment { color: #ddd } 196 | code .init { color: #2F6FAD } 197 | code .string { color: #5890AD } 198 | code .keyword { color: #8A6343 } 199 | code .number { color: #2F6FAD } 200 | -------------------------------------------------------------------------------- /test/assets/js/tests/110.joint.coffee: -------------------------------------------------------------------------------- 1 | 2 | 3 | extend = (target, sources...) -> 4 | for source in sources 5 | for own key, val of source 6 | target[key] = val 7 | target 8 | 9 | describe "Opentip.Joint", -> 10 | 11 | describe "constructor()", -> 12 | it "should forward to set()", -> 13 | sinon.stub Opentip.Joint::, "set" 14 | new Opentip.Joint "abc" 15 | expect(Opentip.Joint::set.args[0][0]).to.be "abc" 16 | Opentip.Joint::set.restore() 17 | it "should accept Pointer objects", -> 18 | p = new Opentip.Joint "top left" 19 | expect(p.toString()).to.be "top left" 20 | p2 = new Opentip.Joint p 21 | expect(p).to.not.be p2 22 | expect(p2.toString()).to.be "top left" 23 | 24 | describe "set()", -> 25 | it "should properly set the positions", -> 26 | p = new Opentip.Joint 27 | p.set "top-left" 28 | expect(p.toString()).to.eql "top left" 29 | 30 | p.set "top-Right" 31 | expect(p.toString()).to.eql "top right" 32 | 33 | p.set "BOTTOM left" 34 | expect(p.toString()).to.eql "bottom left" 35 | 36 | it "should handle any order of positions", -> 37 | p = new Opentip.Joint 38 | p.set "right bottom" 39 | expect(p.toString()).to.eql "bottom right" 40 | 41 | p.set "left left middle" 42 | expect(p.toString()).to.eql "left" 43 | 44 | p.set "left - top" 45 | expect(p.toString()).to.eql "top left" 46 | 47 | it "should add .bottom, .left etc... properties on the position", -> 48 | positions = 49 | top: no 50 | bottom: no 51 | middle: no 52 | left: no 53 | center: no 54 | right: no 55 | 56 | testCount = sinon.stub() 57 | testPointers = (position, thisPositions) -> 58 | thisPositions = extend { }, positions, thisPositions 59 | for positionName, shouldBeTrue of thisPositions 60 | testCount() 61 | if shouldBeTrue then expect(position[positionName]).to.be.ok() 62 | else expect(position[positionName]).to.not.be.ok() 63 | 64 | testPointers (new Opentip.Joint("top")), center: yes, top: yes 65 | testPointers (new Opentip.Joint("top right")), right: yes, top: yes 66 | testPointers (new Opentip.Joint("right")), right: yes, middle: yes 67 | testPointers (new Opentip.Joint("bottom right")), right: yes, bottom: yes 68 | testPointers (new Opentip.Joint("bottom")), center: yes, bottom: yes 69 | testPointers (new Opentip.Joint("bottom left")), left: yes, bottom: yes 70 | testPointers (new Opentip.Joint("left")), left: yes, middle: yes 71 | testPointers (new Opentip.Joint("top left")), left: yes, top: yes 72 | 73 | # Just making sure that the tests are actually called 74 | expect(testCount.callCount).to.be 6 * 8 75 | 76 | describe "setHorizontal()", -> 77 | it "should set the horizontal position", -> 78 | p = new Opentip.Joint "top left" 79 | expect(p.left).to.be.ok(); 80 | expect(p.top).to.be.ok(); 81 | p.setHorizontal "right" 82 | expect(p.left).to.not.be.ok(); 83 | expect(p.top).to.be.ok(); 84 | expect(p.right).to.be.ok(); 85 | 86 | describe "setVertical()", -> 87 | it "should set the vertical position", -> 88 | p = new Opentip.Joint "top left" 89 | expect(p.top).to.be.ok(); 90 | expect(p.left).to.be.ok(); 91 | p.setVertical "bottom" 92 | expect(p.top).to.not.be.ok(); 93 | expect(p.left).to.be.ok(); 94 | expect(p.bottom).to.be.ok(); 95 | 96 | describe "flip()", -> 97 | it "should return itself for chaining", -> 98 | p = new Opentip.Joint "top" 99 | p2 = p.flip() 100 | expect(p).to.be p2 101 | it "should properly flip the position", -> 102 | expect(new Opentip.Joint("top").flip().toString()).to.be "bottom" 103 | expect(new Opentip.Joint("bottomRight").flip().toString()).to.be "top left" 104 | expect(new Opentip.Joint("left top").flip().toString()).to.be "bottom right" 105 | expect(new Opentip.Joint("bottom").flip().toString()).to.be "top" 106 | 107 | -------------------------------------------------------------------------------- /src/adapter.component.coffee: -------------------------------------------------------------------------------- 1 | # Component Opentip Adapter 2 | # ====================== 3 | # 4 | # Uses github.com/component components 5 | 6 | $ = require "jquery" 7 | 8 | # The adapter class 9 | module.exports = class Adapter 10 | 11 | name: "component" 12 | 13 | # Simply using $.domReady 14 | domReady: (callback) -> $ callback 15 | 16 | 17 | # DOM 18 | # === 19 | 20 | # Using bonzo to create html 21 | create: (html) -> $ html 22 | 23 | 24 | # Element handling 25 | # ---------------- 26 | 27 | # Wraps the element in ender 28 | wrap: (element) -> 29 | element = $ element 30 | throw new Error "Multiple elements provided." if element.length > 1 31 | element 32 | 33 | # Returns the unwrapped element 34 | unwrap: (element) -> $(element)[0] 35 | 36 | # Returns the tag name of the element 37 | tagName: (element) -> @unwrap(element).tagName 38 | 39 | # Returns or sets the given attribute of element 40 | # 41 | # It's important not to simply forward name and value because the value 42 | # is set whether or not the value argument is present 43 | attr: (element, args...) -> $(element).attr args... 44 | 45 | # Returns or sets the given data of element 46 | # It's important not to simply forward name and value because the value 47 | # is set whether or not the value argument is present 48 | data: (element, args...) -> $(element).data args... 49 | 50 | # Finds elements by selector 51 | find: (element, selector) -> $(element).find selector 52 | 53 | # Finds all elements by selector 54 | findAll: -> @find.apply @, arguments 55 | 56 | # Updates the content of the element 57 | update: (element, content, escape) -> 58 | element = $ element 59 | if escape 60 | element.text content 61 | else 62 | element.html content 63 | 64 | # Appends given child to element 65 | append: (element, child) -> $(element).append child 66 | 67 | # Add a class 68 | addClass: (element, className) -> $(element).addClass className 69 | 70 | # Remove a class 71 | removeClass: (element, className) -> $(element).removeClass className 72 | 73 | # Set given css properties 74 | css: (element, properties) -> $(element).css properties 75 | 76 | # Returns an object with given dimensions 77 | dimensions: (element) -> 78 | { 79 | width: $(element).outerWidth() 80 | height: $(element).outerHeight() 81 | } 82 | 83 | # Returns the scroll offsets of current document 84 | scrollOffset: -> 85 | [ 86 | window.pageXOffset or document.documentElement.scrollLeft or document.body.scrollLeft 87 | window.pageYOffset or document.documentElement.scrollTop or document.body.scrollTop 88 | ] 89 | 90 | # Returns the dimensions of the viewport (currently visible browser area) 91 | viewportDimensions: -> 92 | { 93 | width: document.documentElement.clientWidth 94 | height: document.documentElement.clientHeight 95 | } 96 | 97 | # Returns an object with x and y 98 | mousePosition: (e) -> 99 | return null unless e? 100 | x: e.pageX, y: e.pageY 101 | 102 | 103 | # Returns the offset of the element 104 | offset: (element) -> 105 | offset = $(element).offset() 106 | { 107 | left: offset.left 108 | top: offset.top 109 | } 110 | 111 | # Observe given eventName 112 | observe: (element, eventName, observer) -> $(element).bind eventName, observer 113 | 114 | # Stop observing event 115 | stopObserving: (element, eventName, observer) -> $(element).unbind eventName, observer 116 | 117 | # Perform an AJAX request and call the appropriate callbacks. 118 | ajax: (options) -> 119 | throw new Error "No url provided" unless options.url? 120 | $.ajax( 121 | url: options.url 122 | type: options.method?.toUpperCase() ? "GET" 123 | ) 124 | .done((content) -> options.onSuccess? content) 125 | .fail((request) -> options.onError? "Server responded with status #{request.status}") 126 | .always(-> options.onComplete?()) 127 | 128 | 129 | # Utility functions 130 | # ================= 131 | 132 | # Creates a shallow copy of the object 133 | clone: (object) -> $.extend { }, object 134 | 135 | # Copies all properties from sources to target 136 | extend: (target, sources...) -> $.extend target, sources... 137 | 138 | 139 | -------------------------------------------------------------------------------- /src/adapter.jquery.coffee: -------------------------------------------------------------------------------- 1 | # jQuery Opentip Adapter 2 | # ====================== 3 | # 4 | # Uses jQuery 5 | 6 | # Because $ is my favorite character 7 | (($) -> 8 | 9 | 10 | # Augment jQuery 11 | $.fn.opentip = (content, title, options) -> 12 | new Opentip this, content, title, options 13 | 14 | 15 | # And now the class 16 | class Adapter 17 | 18 | name: "jquery" 19 | 20 | # Simply using $.domReady 21 | domReady: (callback) -> $ callback 22 | 23 | 24 | # DOM 25 | # === 26 | 27 | # Using bonzo to create html 28 | create: (html) -> $ html 29 | 30 | 31 | # Element handling 32 | # ---------------- 33 | 34 | # Wraps the element in ender 35 | wrap: (element) -> 36 | element = $ element 37 | throw new Error "Multiple elements provided." if element.length > 1 38 | element 39 | 40 | # Returns the unwrapped element 41 | unwrap: (element) -> $(element)[0] 42 | 43 | # Returns the tag name of the element 44 | tagName: (element) -> @unwrap(element).tagName 45 | 46 | # Returns or sets the given attribute of element 47 | # 48 | # It's important not to simply forward name and value because the value 49 | # is set whether or not the value argument is present 50 | attr: (element, args...) -> $(element).attr args... 51 | 52 | # Returns or sets the given data of element 53 | # It's important not to simply forward name and value because the value 54 | # is set whether or not the value argument is present 55 | data: (element, args...) -> $(element).data args... 56 | 57 | # Finds elements by selector 58 | find: (element, selector) -> $(element).find selector 59 | 60 | # Finds all elements by selector 61 | findAll: -> @find.apply @, arguments 62 | 63 | # Updates the content of the element 64 | update: (element, content, escape) -> 65 | element = $ element 66 | if escape 67 | element.text content 68 | else 69 | element.html content 70 | 71 | # Appends given child to element 72 | append: (element, child) -> $(element).append child 73 | 74 | # Add a class 75 | addClass: (element, className) -> $(element).addClass className 76 | 77 | # Remove a class 78 | removeClass: (element, className) -> $(element).removeClass className 79 | 80 | # Set given css properties 81 | css: (element, properties) -> $(element).css properties 82 | 83 | # Returns an object with given dimensions 84 | dimensions: (element) -> 85 | { 86 | width: $(element).outerWidth() 87 | height: $(element).outerHeight() 88 | } 89 | 90 | # Returns the scroll offsets of current document 91 | scrollOffset: -> 92 | [ 93 | window.pageXOffset or document.documentElement.scrollLeft or document.body.scrollLeft 94 | window.pageYOffset or document.documentElement.scrollTop or document.body.scrollTop 95 | ] 96 | 97 | # Returns the dimensions of the viewport (currently visible browser area) 98 | viewportDimensions: -> 99 | { 100 | width: document.documentElement.clientWidth 101 | height: document.documentElement.clientHeight 102 | } 103 | 104 | # Returns an object with x and y 105 | mousePosition: (e) -> 106 | return null unless e? 107 | x: e.pageX, y: e.pageY 108 | 109 | 110 | # Returns the offset of the element 111 | offset: (element) -> 112 | offset = $(element).offset() 113 | { 114 | left: offset.left 115 | top: offset.top 116 | } 117 | 118 | # Observe given eventName 119 | observe: (element, eventName, observer) -> $(element).bind eventName, observer 120 | 121 | # Stop observing event 122 | stopObserving: (element, eventName, observer) -> $(element).unbind eventName, observer 123 | 124 | # Perform an AJAX request and call the appropriate callbacks. 125 | ajax: (options) -> 126 | throw new Error "No url provided" unless options.url? 127 | $.ajax( 128 | url: options.url 129 | type: options.method?.toUpperCase() ? "GET" 130 | ) 131 | .done((content) -> options.onSuccess? content) 132 | .fail((request) -> options.onError? "Server responded with status #{request.status}") 133 | .always(-> options.onComplete?()) 134 | 135 | 136 | # Utility functions 137 | # ================= 138 | 139 | # Creates a shallow copy of the object 140 | clone: (object) -> $.extend { }, object 141 | 142 | # Copies all properties from sources to target 143 | extend: (target, sources...) -> $.extend target, sources... 144 | 145 | # Add the adapter to the list 146 | Opentip.addAdapter new Adapter 147 | 148 | )(jQuery) 149 | -------------------------------------------------------------------------------- /test/views/playground.jade: -------------------------------------------------------------------------------- 1 | !!! 2 | head 3 | meta(charset="utf-8") 4 | 5 | 6 | title Opentip | Playground 7 | 8 | 9 | 10 | | 13 | 14 | != css("stylus/opentip") 15 | 16 | 17 | //- != js('lib/ender') 18 | //- != js('src/opentip') 19 | //- != js('src/adapter.ender') 20 | 21 | 22 | //- != js('lib/jquery-1.8.0') 23 | //- script 24 | //- jQuery.noConflict() 25 | //- != js('lib/prototype-1.7.1') 26 | 27 | != js('downloads/opentip-native-excanvas.js') 28 | //- != js('downloads/opentip-native.js') 29 | //- != js('excanvas') 30 | //- != js('src/opentip') 31 | //- != js('src/adapter.native') 32 | 33 | //- != js('src/adapter.jquery') 34 | 35 | //- != js('src/opentip') 36 | //- != js('src/adapter.native') 37 | 38 | 39 | :coffeescript 40 | Opentip.debug = yes 41 | Opentip.adapter.domReady -> 42 | Opentip.styles.test = 43 | target: true 44 | stem: true 45 | # stemLength: 6 46 | # borderWidth: 1 47 | showOn: "creation" 48 | # hideOn: "click" 49 | hideTrigger: "closeButton" 50 | # closeButtonRadius: 7 51 | # closeButtonOffset: [ -5, 0 ] 52 | # closeButtonCrossLineWidth: 2.4 53 | # closeButtonCrossSize: 5 54 | 55 | # window.x=$(".opentip-test2").opentip("Hallo test", { target: true, tipJoint: "right", borderWidth: 0 }) 56 | 57 | #element = jQuery(".opentip-test")[0] 58 | element = document.querySelector('.opentip-test') 59 | 60 | 61 | window.x = new Opentip element, "Hallo test", { style: "test", tipJoint: "top" } 62 | window.x = new Opentip element, "Hallo test", { style: "test", tipJoint: "top right" } 63 | # window.x = new Opentip element, "Hallo test", { style: "test", tipJoint: "right" } 64 | window.x = new Opentip element, "Hallo test", { style: "test", tipJoint: "right" } 65 | window.x = new Opentip element, "Hallo test", { style: "test", tipJoint: "bottom right" } 66 | window.x = new Opentip element, "Hallo test", { style: "test", tipJoint: "bottom" } 67 | # window.x = new Opentip element, "Hallo test", { style: "test", tipJoint: "bottom left" } 68 | window.x = new Opentip element, "Hallo test", { style: "dark", target: true, stem: true, showOn: "creation", hideTrigger: "closeButton", tipJoint: "left" } 69 | window.x = new Opentip element, "Hallo test", { style: "alert", target: true, stem: true, showOn: "creation", hideTrigger: "closeButton", tipJoint: "top left" } 70 | window.x = new Opentip element, "Hallo test", { style: "glass", target: true, stem: true, showOn: "creation", hideTrigger: "closeButton", tipJoint: "bottom left" } 71 | 72 | window.x = new Opentip element, "Hallo test", { style: "dark", hideDelay: 0.5, tipJoint: "left", stem: true, borderWidth: 2} 73 | 74 | #element = jQuery(".opentip-test2")[0] 75 | element = document.querySelector('.opentip-test2') 76 | window.x = new Opentip(element, "Hallo test", { style: "dark", stem: true, target: true, delay: 0, showEffect: false, hideDelay: 0, hideEffectDuration: 3 }) 77 | 78 | window.x = new Opentip(element, "Hallo test bla test bl
kjsdlfksjdlf", { style: "dark", hideTrigger: "closeButton", stem: true, tipJoint: "top right", target: true, delay: 0, hideDelay: 0, showOn: "creation", borderRadius: 15 }) 79 | 80 | 81 | element = document.querySelector('.opentip-trigger1') 82 | new Opentip element, "Hallo test", { showOn: "mouseover", hideTriggers: [ "trigger", "tip" ], hideOn: [ "mouseout", "click" ], hideDelay: 1, target: true } 83 | 84 | 85 | 86 | :stylus 87 | body 88 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 89 | padding: 60px 50px; 90 | 91 | .test 92 | width 150px 93 | background #f4f4f4 94 | padding 10px 95 | text-align center 96 | position relative 97 | margin 40px auto 98 | 99 | .opentip-container .opentip 100 | padding 10px 15px 101 | 102 | //- canvas { 103 | //- background: rgba(255, 0, 0, 0.1); 104 | //- } 105 | 106 | //- a.close { 107 | //- background: rgba(255, 0,0,0.2); 108 | //- } 109 | 110 | 111 | body(data-test="yes") 112 | 113 | #main 114 | 115 | .test.opentip-test(data-ot="test",data-ot-hide-on="click",data-ot-tip-joint="bottom left") Hello first 116 | .test.opentip-test2(data-ot="test", data-ot-hide-trigger="closeButton", data-ot-target="true", data-ot-tip-joint="top") Hello 117 | 118 | .test 119 | a(href="/ajax-test-delayed",data-ot="", data-ot-style="glass", data-ot-ajax="yes", data-ot-show-on="click", data-ot-hide-on="click") AJAX 120 | 121 | 122 | 123 | .test.opentip-trigger1 Hello1 124 | .test.opentip-trigger2 Hello2 125 | 126 | 127 | -------------------------------------------------------------------------------- /lib/adapter.component.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.4.0 2 | var $, Adapter, 3 | __slice = [].slice; 4 | 5 | $ = require("jquery"); 6 | 7 | module.exports = Adapter = (function() { 8 | 9 | function Adapter() {} 10 | 11 | Adapter.prototype.name = "component"; 12 | 13 | Adapter.prototype.domReady = function(callback) { 14 | return $(callback); 15 | }; 16 | 17 | Adapter.prototype.create = function(html) { 18 | return $(html); 19 | }; 20 | 21 | Adapter.prototype.wrap = function(element) { 22 | element = $(element); 23 | if (element.length > 1) { 24 | throw new Error("Multiple elements provided."); 25 | } 26 | return element; 27 | }; 28 | 29 | Adapter.prototype.unwrap = function(element) { 30 | return $(element)[0]; 31 | }; 32 | 33 | Adapter.prototype.tagName = function(element) { 34 | return this.unwrap(element).tagName; 35 | }; 36 | 37 | Adapter.prototype.attr = function() { 38 | var args, element, _ref; 39 | element = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 40 | return (_ref = $(element)).attr.apply(_ref, args); 41 | }; 42 | 43 | Adapter.prototype.data = function() { 44 | var args, element, _ref; 45 | element = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 46 | return (_ref = $(element)).data.apply(_ref, args); 47 | }; 48 | 49 | Adapter.prototype.find = function(element, selector) { 50 | return $(element).find(selector); 51 | }; 52 | 53 | Adapter.prototype.findAll = function() { 54 | return this.find.apply(this, arguments); 55 | }; 56 | 57 | Adapter.prototype.update = function(element, content, escape) { 58 | element = $(element); 59 | if (escape) { 60 | return element.text(content); 61 | } else { 62 | return element.html(content); 63 | } 64 | }; 65 | 66 | Adapter.prototype.append = function(element, child) { 67 | return $(element).append(child); 68 | }; 69 | 70 | Adapter.prototype.addClass = function(element, className) { 71 | return $(element).addClass(className); 72 | }; 73 | 74 | Adapter.prototype.removeClass = function(element, className) { 75 | return $(element).removeClass(className); 76 | }; 77 | 78 | Adapter.prototype.css = function(element, properties) { 79 | return $(element).css(properties); 80 | }; 81 | 82 | Adapter.prototype.dimensions = function(element) { 83 | return { 84 | width: $(element).outerWidth(), 85 | height: $(element).outerHeight() 86 | }; 87 | }; 88 | 89 | Adapter.prototype.scrollOffset = function() { 90 | return [window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop]; 91 | }; 92 | 93 | Adapter.prototype.viewportDimensions = function() { 94 | return { 95 | width: document.documentElement.clientWidth, 96 | height: document.documentElement.clientHeight 97 | }; 98 | }; 99 | 100 | Adapter.prototype.mousePosition = function(e) { 101 | if (e == null) { 102 | return null; 103 | } 104 | return { 105 | x: e.pageX, 106 | y: e.pageY 107 | }; 108 | }; 109 | 110 | Adapter.prototype.offset = function(element) { 111 | var offset; 112 | offset = $(element).offset(); 113 | return { 114 | left: offset.left, 115 | top: offset.top 116 | }; 117 | }; 118 | 119 | Adapter.prototype.observe = function(element, eventName, observer) { 120 | return $(element).bind(eventName, observer); 121 | }; 122 | 123 | Adapter.prototype.stopObserving = function(element, eventName, observer) { 124 | return $(element).unbind(eventName, observer); 125 | }; 126 | 127 | Adapter.prototype.ajax = function(options) { 128 | var _ref, _ref1; 129 | if (options.url == null) { 130 | throw new Error("No url provided"); 131 | } 132 | return $.ajax({ 133 | url: options.url, 134 | type: (_ref = (_ref1 = options.method) != null ? _ref1.toUpperCase() : void 0) != null ? _ref : "GET" 135 | }).done(function(content) { 136 | return typeof options.onSuccess === "function" ? options.onSuccess(content) : void 0; 137 | }).fail(function(request) { 138 | return typeof options.onError === "function" ? options.onError("Server responded with status " + request.status) : void 0; 139 | }).always(function() { 140 | return typeof options.onComplete === "function" ? options.onComplete() : void 0; 141 | }); 142 | }; 143 | 144 | Adapter.prototype.clone = function(object) { 145 | return $.extend({}, object); 146 | }; 147 | 148 | Adapter.prototype.extend = function() { 149 | var sources, target; 150 | target = arguments[0], sources = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 151 | return $.extend.apply($, [target].concat(__slice.call(sources))); 152 | }; 153 | 154 | return Adapter; 155 | 156 | })(); 157 | -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | # ** Cakefile Template ** is a Template for a common Cakefile that you may use in a coffeescript nodejs project. 2 | # 3 | # It comes baked in with 5 tasks: 4 | # 5 | # * build - compiles your src directory to your lib directory 6 | # * watch - watches any changes in your src directory and automatically compiles to the lib directory 7 | # * test - runs mocha test framework, you can edit this task to use your favorite test framework 8 | # * docs - generates annotated documentation using docco 9 | # * clean - clean generated .js files 10 | files = [ 11 | 'lib' 12 | 'src' 13 | ] 14 | 15 | fs = require 'fs' 16 | {print} = require 'util' 17 | {spawn, exec} = require 'child_process' 18 | 19 | try 20 | which = require('which').sync 21 | catch err 22 | if process.platform.match(/^win/)? 23 | console.log 'WARNING: the which module is required for windows\ntry: npm install which' 24 | which = null 25 | 26 | # ANSI Terminal Colors 27 | bold = '\x1b[0;1m' 28 | green = '\x1b[0;32m' 29 | reset = '\x1b[0m' 30 | red = '\x1b[0;31m' 31 | 32 | task 'docs', 'generate documentation', -> docco() 33 | 34 | task 'build', 'compile source', -> build -> log ":)", green 35 | 36 | task 'watch', 'compile and watch', -> build true, -> log ":-)", green 37 | 38 | task 'css', 'compile stylus', -> css -> log ":)", green 39 | 40 | task 'watchcss', 'compile and watch stylus', -> css true, -> log ":-)", green 41 | 42 | 43 | # Internal Functions 44 | # 45 | # ## *walk* 46 | # 47 | # **given** string as dir which represents a directory in relation to local directory 48 | # **and** callback as done in the form of (err, results) 49 | # **then** recurse through directory returning an array of files 50 | # 51 | # Examples 52 | # 53 | # ``` coffeescript 54 | # walk 'src', (err, results) -> console.log results 55 | # ``` 56 | walk = (dir, done) -> 57 | results = [] 58 | fs.readdir dir, (err, list) -> 59 | return done(err, []) if err 60 | pending = list.length 61 | return done(null, results) unless pending 62 | for name in list 63 | file = "#{dir}/#{name}" 64 | try 65 | stat = fs.statSync file 66 | catch err 67 | stat = null 68 | if stat?.isDirectory() 69 | walk file, (err, res) -> 70 | results.push name for name in res 71 | done(null, results) unless --pending 72 | else 73 | results.push file 74 | done(null, results) unless --pending 75 | 76 | # ## *log* 77 | # 78 | # **given** string as a message 79 | # **and** string as a color 80 | # **and** optional string as an explaination 81 | # **then** builds a statement and logs to console. 82 | # 83 | log = (message, color, explanation) -> console.log color + message + reset + ' ' + (explanation or '') 84 | 85 | # ## *launch* 86 | # 87 | # **given** string as a cmd 88 | # **and** optional array and option flags 89 | # **and** optional callback 90 | # **then** spawn cmd with options 91 | # **and** pipe to process stdout and stderr respectively 92 | # **and** on child process exit emit callback if set and status is 0 93 | launch = (cmd, options=[], callback) -> 94 | cmd = which(cmd) if which 95 | app = spawn cmd, options 96 | app.stdout.pipe(process.stdout) 97 | app.stderr.pipe(process.stderr) 98 | app.on 'exit', (status) -> callback?() if status is 0 99 | 100 | # ## *build* 101 | # 102 | # **given** optional boolean as watch 103 | # **and** optional function as callback 104 | # **then** invoke launch passing coffee command 105 | # **and** defaulted options to compile src to lib 106 | build = (watch, callback) -> 107 | if typeof watch is 'function' 108 | callback = watch 109 | watch = false 110 | 111 | options = ['-c', '-b', '-o' ] 112 | options = options.concat files 113 | options.unshift '-w' if watch 114 | launch "#{__dirname}/node_modules/coffee-script/bin/coffee", options, callback 115 | 116 | # ## *unlinkIfCoffeeFile* 117 | # 118 | # **given** string as file 119 | # **and** file ends in '.coffee' 120 | # **then** convert '.coffee' to '.js' 121 | # **and** remove the result 122 | unlinkIfCoffeeFile = (file) -> 123 | if file.match /\.coffee$/ 124 | fs.unlink file.replace(/\.coffee$/, '.js') 125 | true 126 | else false 127 | 128 | # ## *moduleExists* 129 | # 130 | # **given** name for module 131 | # **when** trying to require module 132 | # **and** not found 133 | # **then* print not found message with install helper in red 134 | # **and* return false if not found 135 | moduleExists = (name) -> 136 | try 137 | require name 138 | catch err 139 | log "#{name} required: npm install #{name}", red 140 | false 141 | 142 | 143 | # ## *docco* 144 | # 145 | # **given** optional function as callback 146 | # **then** invoke launch passing docco command 147 | docco = (callback) -> 148 | #if moduleExists('docco') 149 | walk 'src', (err, files) -> launch 'docco', files, callback 150 | 151 | 152 | css = (watch, callback) -> 153 | if typeof watch is 'function' 154 | callback = watch 155 | watch = false 156 | 157 | options = [ "-o", "#{__dirname}/css/", "--include", "#{__dirname}/node_modules/nib/lib" ] 158 | options.push "-w" if watch 159 | options.push "#{__dirname}/css/stylus" 160 | 161 | launch "#{__dirname}/node_modules/stylus/bin/stylus", options, callback 162 | 163 | -------------------------------------------------------------------------------- /lib/adapter.jquery.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.4.0 2 | var __slice = [].slice; 3 | 4 | (function($) { 5 | var Adapter; 6 | $.fn.opentip = function(content, title, options) { 7 | return new Opentip(this, content, title, options); 8 | }; 9 | Adapter = (function() { 10 | 11 | function Adapter() {} 12 | 13 | Adapter.prototype.name = "jquery"; 14 | 15 | Adapter.prototype.domReady = function(callback) { 16 | return $(callback); 17 | }; 18 | 19 | Adapter.prototype.create = function(html) { 20 | return $(html); 21 | }; 22 | 23 | Adapter.prototype.wrap = function(element) { 24 | element = $(element); 25 | if (element.length > 1) { 26 | throw new Error("Multiple elements provided."); 27 | } 28 | return element; 29 | }; 30 | 31 | Adapter.prototype.unwrap = function(element) { 32 | return $(element)[0]; 33 | }; 34 | 35 | Adapter.prototype.tagName = function(element) { 36 | return this.unwrap(element).tagName; 37 | }; 38 | 39 | Adapter.prototype.attr = function() { 40 | var args, element, _ref; 41 | element = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 42 | return (_ref = $(element)).attr.apply(_ref, args); 43 | }; 44 | 45 | Adapter.prototype.data = function() { 46 | var args, element, _ref; 47 | element = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 48 | return (_ref = $(element)).data.apply(_ref, args); 49 | }; 50 | 51 | Adapter.prototype.find = function(element, selector) { 52 | return $(element).find(selector); 53 | }; 54 | 55 | Adapter.prototype.findAll = function() { 56 | return this.find.apply(this, arguments); 57 | }; 58 | 59 | Adapter.prototype.update = function(element, content, escape) { 60 | element = $(element); 61 | if (escape) { 62 | return element.text(content); 63 | } else { 64 | return element.html(content); 65 | } 66 | }; 67 | 68 | Adapter.prototype.append = function(element, child) { 69 | return $(element).append(child); 70 | }; 71 | 72 | Adapter.prototype.addClass = function(element, className) { 73 | return $(element).addClass(className); 74 | }; 75 | 76 | Adapter.prototype.removeClass = function(element, className) { 77 | return $(element).removeClass(className); 78 | }; 79 | 80 | Adapter.prototype.css = function(element, properties) { 81 | return $(element).css(properties); 82 | }; 83 | 84 | Adapter.prototype.dimensions = function(element) { 85 | return { 86 | width: $(element).outerWidth(), 87 | height: $(element).outerHeight() 88 | }; 89 | }; 90 | 91 | Adapter.prototype.scrollOffset = function() { 92 | return [window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop]; 93 | }; 94 | 95 | Adapter.prototype.viewportDimensions = function() { 96 | return { 97 | width: document.documentElement.clientWidth, 98 | height: document.documentElement.clientHeight 99 | }; 100 | }; 101 | 102 | Adapter.prototype.mousePosition = function(e) { 103 | if (e == null) { 104 | return null; 105 | } 106 | return { 107 | x: e.pageX, 108 | y: e.pageY 109 | }; 110 | }; 111 | 112 | Adapter.prototype.offset = function(element) { 113 | var offset; 114 | offset = $(element).offset(); 115 | return { 116 | left: offset.left, 117 | top: offset.top 118 | }; 119 | }; 120 | 121 | Adapter.prototype.observe = function(element, eventName, observer) { 122 | return $(element).bind(eventName, observer); 123 | }; 124 | 125 | Adapter.prototype.stopObserving = function(element, eventName, observer) { 126 | return $(element).unbind(eventName, observer); 127 | }; 128 | 129 | Adapter.prototype.ajax = function(options) { 130 | var _ref, _ref1; 131 | if (options.url == null) { 132 | throw new Error("No url provided"); 133 | } 134 | return $.ajax({ 135 | url: options.url, 136 | type: (_ref = (_ref1 = options.method) != null ? _ref1.toUpperCase() : void 0) != null ? _ref : "GET" 137 | }).done(function(content) { 138 | return typeof options.onSuccess === "function" ? options.onSuccess(content) : void 0; 139 | }).fail(function(request) { 140 | return typeof options.onError === "function" ? options.onError("Server responded with status " + request.status) : void 0; 141 | }).always(function() { 142 | return typeof options.onComplete === "function" ? options.onComplete() : void 0; 143 | }); 144 | }; 145 | 146 | Adapter.prototype.clone = function(object) { 147 | return $.extend({}, object); 148 | }; 149 | 150 | Adapter.prototype.extend = function() { 151 | var sources, target; 152 | target = arguments[0], sources = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 153 | return $.extend.apply($, [target].concat(__slice.call(sources))); 154 | }; 155 | 156 | return Adapter; 157 | 158 | })(); 159 | return Opentip.addAdapter(new Adapter); 160 | })(jQuery); 161 | -------------------------------------------------------------------------------- /src/adapter.ender.coffee: -------------------------------------------------------------------------------- 1 | # Ender Opentip Adapter 2 | # ===================== 3 | # 4 | # Uses ender packages 5 | 6 | # Because $ is my favorite character 7 | (($) -> 8 | 9 | # Using bean as event handler 10 | bean = require "bean" 11 | 12 | # Using reqwest as AJAX lib 13 | reqwest = require "reqwest" 14 | 15 | # Augment ender 16 | $.ender { 17 | opentip: (content, title, options) -> new Opentip this, content, title, options 18 | }, true 19 | 20 | 21 | # And now the class 22 | class Adapter 23 | 24 | name: "ender" 25 | 26 | # Simply using $.domReady 27 | domReady: (callback) -> $.domReady callback 28 | 29 | 30 | # DOM 31 | # === 32 | 33 | # Using bonzo to create html 34 | create: (html) -> $ html 35 | 36 | 37 | # Element handling 38 | # ---------------- 39 | 40 | # Wraps the element in ender 41 | wrap: (element) -> 42 | element = $ element 43 | throw new Error "Multiple elements provided." if element.length > 1 44 | element 45 | 46 | # Returns the unwrapped element 47 | unwrap: (element) -> $(element).get 0 48 | 49 | # Returns the tag name of the element 50 | tagName: (element) -> @unwrap(element).tagName 51 | 52 | # Returns or sets the given attribute of element 53 | # It's important not to simply forward name and value because the value 54 | # is set whether or not the value argument is present 55 | attr: (element, args...) -> $(element).attr args... 56 | 57 | # Returns or sets the given data of element 58 | # It's important not to simply forward name and value because the value 59 | # is set whether or not the value argument is present 60 | data: (element, args...) -> $(element).data args... 61 | 62 | # Finds elements by selector 63 | find: (element, selector) -> $(element).find selector 64 | 65 | # Finds all elements by selector 66 | findAll: -> @find.apply @, arguments 67 | 68 | # Updates the content of the element 69 | update: (element, content, escape) -> 70 | element = $ element 71 | if escape 72 | element.text content 73 | else 74 | element.html content 75 | 76 | # Appends given child to element 77 | append: (element, child) -> $(element).append child 78 | 79 | # Add a class 80 | addClass: (element, className) -> $(element).addClass className 81 | 82 | # Remove a class 83 | removeClass: (element, className) -> $(element).removeClass className 84 | 85 | # Set given css properties 86 | css: (element, properties) -> $(element).css properties 87 | 88 | # Returns an object with given dimensions 89 | dimensions: (element) -> $(element).dim() 90 | 91 | # Returns the scroll offsets of current document 92 | scrollOffset: -> 93 | [ 94 | window.pageXOffset or document.documentElement.scrollLeft or document.body.scrollLeft 95 | window.pageYOffset or document.documentElement.scrollTop or document.body.scrollTop 96 | ] 97 | 98 | # Returns the dimensions of the viewport (currently visible browser area) 99 | viewportDimensions: -> 100 | { 101 | width: document.documentElement.clientWidth 102 | height: document.documentElement.clientHeight 103 | } 104 | 105 | # Returns an object with x and y 106 | mousePosition: (e) -> 107 | pos = x: 0, y: 0 108 | 109 | e ?= window.event 110 | 111 | return unless e? 112 | 113 | if e.pageX or e.pageY 114 | pos.x = e.pageX 115 | pos.y = e.pageY 116 | else if e.clientX or e.clientY 117 | pos.x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft 118 | pos.y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop 119 | 120 | pos 121 | 122 | # Returns the offset of the element 123 | offset: (element) -> 124 | offset = $(element).offset() 125 | { 126 | top: offset.top 127 | left: offset.left 128 | } 129 | 130 | # Observe given eventName 131 | observe: (element, eventName, observer) -> 132 | $(element).on eventName, observer 133 | 134 | # Stop observing event 135 | stopObserving: (element, eventName, observer) -> $(element).unbind eventName, observer 136 | 137 | # Perform an AJAX request and call the appropriate callbacks. 138 | ajax: (options) -> 139 | throw new Error "No url provided" unless options.url? 140 | reqwest 141 | url: options.url 142 | type: 'html' 143 | method: options.method?.toUpperCase() ? "GET" 144 | error: (resp) -> options.onError? "Server responded with status #{resp.status}" 145 | success: (resp) -> options.onSuccess? resp 146 | complete: -> options.onComplete?() 147 | 148 | 149 | # Utility functions 150 | # ================= 151 | 152 | # Creates a shallow copy of the object 153 | clone: (object) -> 154 | newObject = { } 155 | for own key, val of object 156 | newObject[key] = val 157 | newObject 158 | 159 | # Copies all properties from sources to target 160 | extend: (target, sources...) -> 161 | for source in sources 162 | for own key, val of source 163 | target[key] = val 164 | target 165 | 166 | # Add the adapter to the list 167 | Opentip.addAdapter new Adapter 168 | 169 | )(ender) 170 | -------------------------------------------------------------------------------- /src/adapter.prototype.coffee: -------------------------------------------------------------------------------- 1 | # Prototype Opentip Adapter 2 | # ====================== 3 | # 4 | # Uses the prototype framework 5 | 6 | do -> 7 | 8 | Element.addMethods 9 | addTip: (element, content, title, options) -> 10 | new Opentip element, content, title, options 11 | 12 | 13 | # Needs this function because of IE8 14 | isArrayOrNodeList = (element) -> 15 | if (element instanceof Array) or (element? and typeof element.length == 'number' and typeof element.item == 'function' and typeof element.nextNode == 'function' and typeof element.reset == 'function') 16 | return yes 17 | return no 18 | 19 | # And now the class 20 | class Adapter 21 | 22 | name: "prototype" 23 | 24 | domReady: (callback) -> 25 | if document.loaded 26 | callback() 27 | else 28 | $(document).observe "dom:loaded", callback 29 | 30 | 31 | # DOM 32 | # === 33 | 34 | # Using bonzo to create html 35 | create: (html) -> new Element('div').update(html).childElements() 36 | 37 | 38 | # Element handling 39 | # ---------------- 40 | 41 | # Wraps the element 42 | wrap: (element) -> 43 | if isArrayOrNodeList element 44 | throw new Error "Multiple elements provided." if element.length > 1 45 | element = @unwrap element 46 | $(element) 47 | element 48 | 49 | # Returns the unwrapped element 50 | unwrap: (element) -> 51 | if isArrayOrNodeList element 52 | element[0] 53 | else 54 | element 55 | 56 | # Returns the tag name of the element 57 | tagName: (element) -> @unwrap(element).tagName 58 | 59 | # Returns or sets the given attribute of element 60 | # 61 | # It's important not to simply forward name and value because the value 62 | # is set whether or not the value argument is present 63 | attr: (element, args...) -> 64 | if args.length == 1 65 | @wrap(element).readAttribute args[0] 66 | else 67 | @wrap(element).writeAttribute args... 68 | 69 | # Returns or sets the given data of element 70 | # It's important not to simply forward name and value because the value 71 | # is set whether or not the value argument is present 72 | data: (element, name, value) -> 73 | @wrap(element) 74 | if arguments.length > 2 75 | element.store name, value 76 | else 77 | arg = element.readAttribute "data-#{name.underscore().dasherize()}" 78 | return arg if arg? 79 | element.retrieve name 80 | 81 | # Finds elements by selector 82 | find: (element, selector) -> @wrap(element).select(selector)[0] 83 | 84 | # Finds all elements by selector 85 | findAll: (element, selector) -> @wrap(element).select selector 86 | 87 | # Updates the content of the element 88 | update: (element, content, escape) -> 89 | @wrap(element).update if escape then content.escapeHTML() else content 90 | 91 | # Appends given child to element 92 | append: (element, child) -> @wrap(element).insert @wrap child 93 | 94 | # Add a class 95 | addClass: (element, className) -> @wrap(element).addClassName className 96 | 97 | # Remove a class 98 | removeClass: (element, className) -> @wrap(element).removeClassName className 99 | 100 | # Set given css properties 101 | css: (element, properties) -> @wrap(element).setStyle properties 102 | 103 | # Returns an object with given dimensions 104 | dimensions: (element) -> @wrap(element).getDimensions() 105 | 106 | # Returns the scroll offsets of current document 107 | scrollOffset: -> 108 | offsets = document.viewport.getScrollOffsets() 109 | [ offsets.left, offsets.top ] 110 | 111 | # Returns the dimensions of the viewport (currently visible browser area) 112 | viewportDimensions: -> document.viewport.getDimensions() 113 | 114 | # Returns an object with x and y 115 | mousePosition: (e) -> 116 | return null unless e? 117 | x: Event.pointerX(e), y: Event.pointerY(e) 118 | 119 | 120 | # Returns the offset of the element 121 | offset: (element) -> 122 | offset = @wrap(element).cumulativeOffset() 123 | left: offset.left, top: offset.top 124 | 125 | # Observe given eventName 126 | observe: (element, eventName, observer) -> Event.observe @wrap(element), eventName, observer 127 | 128 | # Stop observing event 129 | stopObserving: (element, eventName, observer) -> Event.stopObserving @wrap(element), eventName, observer 130 | 131 | # Perform an AJAX request and call the appropriate callbacks. 132 | ajax: (options) -> 133 | throw new Error "No url provided" unless options.url? 134 | 135 | new Ajax.Request options.url, { 136 | method: options.method?.toUpperCase() ? "GET" 137 | onSuccess: (response) -> options.onSuccess? response.responseText 138 | onFailure: (response) -> options.onError? "Server responded with status #{response.status}" 139 | onComplete: -> options.onComplete?() 140 | } 141 | 142 | 143 | # Utility functions 144 | # ================= 145 | 146 | # Creates a shallow copy of the object 147 | clone: (object) -> Object.clone(object) 148 | 149 | # Copies all properties from sources to target 150 | extend: (target, sources...) -> 151 | for source in sources 152 | Object.extend target, source 153 | target 154 | 155 | # Add the adapter to the list 156 | Opentip.addAdapter new Adapter 157 | 158 | -------------------------------------------------------------------------------- /docs/adapter.prototype.html: -------------------------------------------------------------------------------- 1 | adapter.prototype.coffee

adapter.prototype.coffee

$ = ender
 2 | 
 3 | 
 4 | class Adapter
 5 | 
 6 |   name: "prototype"

Simply using $.domReady

  domReady: (callback) -> Event.observe window, "dom:loaded", callback

Using bonzo to create html

  create: (html) -> $ html

Mimics scriptaculous Builder.node behaviour 7 | element: (tagName, attributes, children) -> 8 | if Object.isArray(attributes) or Object.isString(attributes) or Object.isElement(attributes) 9 | children = attributes 10 | attributes = null 11 | element = new Element(tagName, attributes or {})

    

# This is a prototype 1.6 bug, that doesn't apply the className to IE8 elements. 12 | # Thanks to Alexander Shakhnovsky for finding the bug, and pinpointing the problem. 13 | if attributes and attributes["className"] 14 | attributes["className"].split(" ").each (classname) -> 15 | element.addClassName classname

if children 16 | if Object.isArray(children) 17 | children.each (child) -> 18 | element.insert bottom: child

else
19 |   element.insert bottom: children
20 | 
21 | 22 |

element

adapter = new Adapter
23 | 
24 | Opentip.addAdapter adapter
25 | 
26 | 
-------------------------------------------------------------------------------- /lib/adapter.ender.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.4.0 2 | var __slice = [].slice, 3 | __hasProp = {}.hasOwnProperty; 4 | 5 | (function($) { 6 | var Adapter, bean, reqwest; 7 | bean = require("bean"); 8 | reqwest = require("reqwest"); 9 | $.ender({ 10 | opentip: function(content, title, options) { 11 | return new Opentip(this, content, title, options); 12 | } 13 | }, true); 14 | Adapter = (function() { 15 | 16 | function Adapter() {} 17 | 18 | Adapter.prototype.name = "ender"; 19 | 20 | Adapter.prototype.domReady = function(callback) { 21 | return $.domReady(callback); 22 | }; 23 | 24 | Adapter.prototype.create = function(html) { 25 | return $(html); 26 | }; 27 | 28 | Adapter.prototype.wrap = function(element) { 29 | element = $(element); 30 | if (element.length > 1) { 31 | throw new Error("Multiple elements provided."); 32 | } 33 | return element; 34 | }; 35 | 36 | Adapter.prototype.unwrap = function(element) { 37 | return $(element).get(0); 38 | }; 39 | 40 | Adapter.prototype.tagName = function(element) { 41 | return this.unwrap(element).tagName; 42 | }; 43 | 44 | Adapter.prototype.attr = function() { 45 | var args, element, _ref; 46 | element = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 47 | return (_ref = $(element)).attr.apply(_ref, args); 48 | }; 49 | 50 | Adapter.prototype.data = function() { 51 | var args, element, _ref; 52 | element = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 53 | return (_ref = $(element)).data.apply(_ref, args); 54 | }; 55 | 56 | Adapter.prototype.find = function(element, selector) { 57 | return $(element).find(selector); 58 | }; 59 | 60 | Adapter.prototype.findAll = function() { 61 | return this.find.apply(this, arguments); 62 | }; 63 | 64 | Adapter.prototype.update = function(element, content, escape) { 65 | element = $(element); 66 | if (escape) { 67 | return element.text(content); 68 | } else { 69 | return element.html(content); 70 | } 71 | }; 72 | 73 | Adapter.prototype.append = function(element, child) { 74 | return $(element).append(child); 75 | }; 76 | 77 | Adapter.prototype.addClass = function(element, className) { 78 | return $(element).addClass(className); 79 | }; 80 | 81 | Adapter.prototype.removeClass = function(element, className) { 82 | return $(element).removeClass(className); 83 | }; 84 | 85 | Adapter.prototype.css = function(element, properties) { 86 | return $(element).css(properties); 87 | }; 88 | 89 | Adapter.prototype.dimensions = function(element) { 90 | return $(element).dim(); 91 | }; 92 | 93 | Adapter.prototype.scrollOffset = function() { 94 | return [window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop]; 95 | }; 96 | 97 | Adapter.prototype.viewportDimensions = function() { 98 | return { 99 | width: document.documentElement.clientWidth, 100 | height: document.documentElement.clientHeight 101 | }; 102 | }; 103 | 104 | Adapter.prototype.mousePosition = function(e) { 105 | var pos; 106 | pos = { 107 | x: 0, 108 | y: 0 109 | }; 110 | if (e == null) { 111 | e = window.event; 112 | } 113 | if (e == null) { 114 | return; 115 | } 116 | if (e.pageX || e.pageY) { 117 | pos.x = e.pageX; 118 | pos.y = e.pageY; 119 | } else if (e.clientX || e.clientY) { 120 | pos.x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; 121 | pos.y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; 122 | } 123 | return pos; 124 | }; 125 | 126 | Adapter.prototype.offset = function(element) { 127 | var offset; 128 | offset = $(element).offset(); 129 | return { 130 | top: offset.top, 131 | left: offset.left 132 | }; 133 | }; 134 | 135 | Adapter.prototype.observe = function(element, eventName, observer) { 136 | return $(element).on(eventName, observer); 137 | }; 138 | 139 | Adapter.prototype.stopObserving = function(element, eventName, observer) { 140 | return $(element).unbind(eventName, observer); 141 | }; 142 | 143 | Adapter.prototype.ajax = function(options) { 144 | var _ref, _ref1; 145 | if (options.url == null) { 146 | throw new Error("No url provided"); 147 | } 148 | return reqwest({ 149 | url: options.url, 150 | type: 'html', 151 | method: (_ref = (_ref1 = options.method) != null ? _ref1.toUpperCase() : void 0) != null ? _ref : "GET", 152 | error: function(resp) { 153 | return typeof options.onError === "function" ? options.onError("Server responded with status " + resp.status) : void 0; 154 | }, 155 | success: function(resp) { 156 | return typeof options.onSuccess === "function" ? options.onSuccess(resp) : void 0; 157 | }, 158 | complete: function() { 159 | return typeof options.onComplete === "function" ? options.onComplete() : void 0; 160 | } 161 | }); 162 | }; 163 | 164 | Adapter.prototype.clone = function(object) { 165 | var key, newObject, val; 166 | newObject = {}; 167 | for (key in object) { 168 | if (!__hasProp.call(object, key)) continue; 169 | val = object[key]; 170 | newObject[key] = val; 171 | } 172 | return newObject; 173 | }; 174 | 175 | Adapter.prototype.extend = function() { 176 | var key, source, sources, target, val, _i, _len; 177 | target = arguments[0], sources = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 178 | for (_i = 0, _len = sources.length; _i < _len; _i++) { 179 | source = sources[_i]; 180 | for (key in source) { 181 | if (!__hasProp.call(source, key)) continue; 182 | val = source[key]; 183 | target[key] = val; 184 | } 185 | } 186 | return target; 187 | }; 188 | 189 | return Adapter; 190 | 191 | })(); 192 | return Opentip.addAdapter(new Adapter); 193 | })(ender); 194 | -------------------------------------------------------------------------------- /lib/adapter.prototype.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.4.0 2 | var __slice = [].slice; 3 | 4 | (function() { 5 | var Adapter, isArrayOrNodeList; 6 | Element.addMethods({ 7 | addTip: function(element, content, title, options) { 8 | return new Opentip(element, content, title, options); 9 | } 10 | }); 11 | isArrayOrNodeList = function(element) { 12 | if ((element instanceof Array) || ((element != null) && typeof element.length === 'number' && typeof element.item === 'function' && typeof element.nextNode === 'function' && typeof element.reset === 'function')) { 13 | return true; 14 | } 15 | return false; 16 | }; 17 | Adapter = (function() { 18 | 19 | function Adapter() {} 20 | 21 | Adapter.prototype.name = "prototype"; 22 | 23 | Adapter.prototype.domReady = function(callback) { 24 | if (document.loaded) { 25 | return callback(); 26 | } else { 27 | return $(document).observe("dom:loaded", callback); 28 | } 29 | }; 30 | 31 | Adapter.prototype.create = function(html) { 32 | return new Element('div').update(html).childElements(); 33 | }; 34 | 35 | Adapter.prototype.wrap = function(element) { 36 | if (isArrayOrNodeList(element)) { 37 | if (element.length > 1) { 38 | throw new Error("Multiple elements provided."); 39 | } 40 | element = this.unwrap(element); 41 | } 42 | $(element); 43 | return element; 44 | }; 45 | 46 | Adapter.prototype.unwrap = function(element) { 47 | if (isArrayOrNodeList(element)) { 48 | return element[0]; 49 | } else { 50 | return element; 51 | } 52 | }; 53 | 54 | Adapter.prototype.tagName = function(element) { 55 | return this.unwrap(element).tagName; 56 | }; 57 | 58 | Adapter.prototype.attr = function() { 59 | var args, element, _ref; 60 | element = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 61 | if (args.length === 1) { 62 | return this.wrap(element).readAttribute(args[0]); 63 | } else { 64 | return (_ref = this.wrap(element)).writeAttribute.apply(_ref, args); 65 | } 66 | }; 67 | 68 | Adapter.prototype.data = function(element, name, value) { 69 | var arg; 70 | this.wrap(element); 71 | if (arguments.length > 2) { 72 | return element.store(name, value); 73 | } else { 74 | arg = element.readAttribute("data-" + (name.underscore().dasherize())); 75 | if (arg != null) { 76 | return arg; 77 | } 78 | return element.retrieve(name); 79 | } 80 | }; 81 | 82 | Adapter.prototype.find = function(element, selector) { 83 | return this.wrap(element).select(selector)[0]; 84 | }; 85 | 86 | Adapter.prototype.findAll = function(element, selector) { 87 | return this.wrap(element).select(selector); 88 | }; 89 | 90 | Adapter.prototype.update = function(element, content, escape) { 91 | return this.wrap(element).update(escape ? content.escapeHTML() : content); 92 | }; 93 | 94 | Adapter.prototype.append = function(element, child) { 95 | return this.wrap(element).insert(this.wrap(child)); 96 | }; 97 | 98 | Adapter.prototype.addClass = function(element, className) { 99 | return this.wrap(element).addClassName(className); 100 | }; 101 | 102 | Adapter.prototype.removeClass = function(element, className) { 103 | return this.wrap(element).removeClassName(className); 104 | }; 105 | 106 | Adapter.prototype.css = function(element, properties) { 107 | return this.wrap(element).setStyle(properties); 108 | }; 109 | 110 | Adapter.prototype.dimensions = function(element) { 111 | return this.wrap(element).getDimensions(); 112 | }; 113 | 114 | Adapter.prototype.scrollOffset = function() { 115 | var offsets; 116 | offsets = document.viewport.getScrollOffsets(); 117 | return [offsets.left, offsets.top]; 118 | }; 119 | 120 | Adapter.prototype.viewportDimensions = function() { 121 | return document.viewport.getDimensions(); 122 | }; 123 | 124 | Adapter.prototype.mousePosition = function(e) { 125 | if (e == null) { 126 | return null; 127 | } 128 | return { 129 | x: Event.pointerX(e), 130 | y: Event.pointerY(e) 131 | }; 132 | }; 133 | 134 | Adapter.prototype.offset = function(element) { 135 | var offset; 136 | offset = this.wrap(element).cumulativeOffset(); 137 | return { 138 | left: offset.left, 139 | top: offset.top 140 | }; 141 | }; 142 | 143 | Adapter.prototype.observe = function(element, eventName, observer) { 144 | return Event.observe(this.wrap(element), eventName, observer); 145 | }; 146 | 147 | Adapter.prototype.stopObserving = function(element, eventName, observer) { 148 | return Event.stopObserving(this.wrap(element), eventName, observer); 149 | }; 150 | 151 | Adapter.prototype.ajax = function(options) { 152 | var _ref, _ref1; 153 | if (options.url == null) { 154 | throw new Error("No url provided"); 155 | } 156 | return new Ajax.Request(options.url, { 157 | method: (_ref = (_ref1 = options.method) != null ? _ref1.toUpperCase() : void 0) != null ? _ref : "GET", 158 | onSuccess: function(response) { 159 | return typeof options.onSuccess === "function" ? options.onSuccess(response.responseText) : void 0; 160 | }, 161 | onFailure: function(response) { 162 | return typeof options.onError === "function" ? options.onError("Server responded with status " + response.status) : void 0; 163 | }, 164 | onComplete: function() { 165 | return typeof options.onComplete === "function" ? options.onComplete() : void 0; 166 | } 167 | }); 168 | }; 169 | 170 | Adapter.prototype.clone = function(object) { 171 | return Object.clone(object); 172 | }; 173 | 174 | Adapter.prototype.extend = function() { 175 | var source, sources, target, _i, _len; 176 | target = arguments[0], sources = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 177 | for (_i = 0, _len = sources.length; _i < _len; _i++) { 178 | source = sources[_i]; 179 | Object.extend(target, source); 180 | } 181 | return target; 182 | }; 183 | 184 | return Adapter; 185 | 186 | })(); 187 | return Opentip.addAdapter(new Adapter); 188 | })(); 189 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Opentip 2 | ======= 3 | 4 | [Opentip][opentip] is a free opensource Java-Script tooltip class. 5 | 6 | 7 | Features 8 | -------- 9 | 10 | It supports: 11 | 12 | - Stems (little pointers) 13 | - Automatic content download with [AJAX][ajax] 14 | - Different styles 15 | - Automatic repositioning of the tooltip if it's not in the viewport of the browser anymore 16 | - All kind of triggers (The tooltip can be triggered by mouse over, click, form submit,... everything you can think of really) 17 | - CSS3 Animations 18 | - Well tested, with over 200 unit tests 19 | 20 | As of Version 2.0 Opentip does **no longer depend on [Prototype]**. You can choose 21 | *any* adapter you want so you can work with the framework of your choice. 22 | 23 | Supported frameworks are: 24 | 25 | - Native. You can use this one if you don't use any framework. 26 | - [Ender] 27 | - [Component] 28 | - [jQuery] 29 | - [Prototype] 30 | 31 | 32 | > If you want to contribute, please read on in the [contribute](https://github.com/enyo/opentip/blob/master/CONTRIBUTING.md) 33 | > file. If you are migrating from version **1.x** please refer to the 34 | > [migration section](#migrating-from-opentip-1x-to-2x) 35 | 36 | Installation 37 | ------------ 38 | 39 | ### jQuery, Prototype, Native 40 | 41 | Just download `lib/opentip.js` and `lib/adapter.FRAMEWORK.js` and include them 42 | in this order. You can also take the already minified and combined files in the 43 | `downloads/` folder. 44 | 45 | ### Component 46 | 47 | The easiest and recommended way to install *opentip* is with [component]. Just 48 | add `enyo/opentip` as dependency in your `component.json` and rebuild it. 49 | 50 | Simply requiring opentip then activates the tooltips: `require "opentip";` 51 | 52 | 53 | ### Ender 54 | 55 | If you prefer [ender] as package manager just install it like this: 56 | 57 | ```bash 58 | $ ender build opentip 59 | ``` 60 | 61 | * * * 62 | 63 | You should include opentip's CSS as well. It's in `css/opentip.css`. (Except 64 | for [component] of course which automatically bundles the css.) 65 | 66 | * * * 67 | 68 | If you want to work it with <=IE8, you have to include excanvas as well. Please 69 | refer to the [installation guide](http://www.opentip.org/installation.html). 70 | 71 | Usage 72 | ----- 73 | 74 | *Version 2.2.7* 75 | 76 | With HTML data attributes: 77 | 78 | ```html 79 |
Click me
80 | ``` 81 | 82 | or with the Javascript API: 83 | 84 | ```js 85 | $('elementId').opentip('Content', { showOn: "click", ...options... }); 86 | ``` 87 | 88 | For the complete documentation please visit [www.opentip.org][opentip]. 89 | 90 | 91 | Future plans 92 | ------------ 93 | 94 | - ~~Become library independant. I'm currently working on 95 | extracting all prototype functionality, so I can switch library easily. The 96 | next library I'll support will be jquery, and then mootools.~~ 97 | 98 | - Add more skins. 99 | 100 | - ~~Add cooler loading animation.~~ 101 | 102 | - ~~Implement unit tests.~~ 103 | 104 | 105 | If you have ideas, please contact me! 106 | 107 | 108 | Contribute 109 | ---------- 110 | 111 | Please refer to the [CONTRIBUTING](https://github.com/enyo/opentip/blob/develop/CONTRIBUTING.md) readme. 112 | 113 | 114 | 115 | Migrating from Opentip 1.x to 2.x 116 | --------------------------------- 117 | 118 | Those are the major changes you should look out for when migrating from 1.x to 2.x: 119 | 120 | - There's no `Tip` or `Tips` object anymore. Everything is done through 121 | `Opentip` 122 | 123 | - The recommend way to create opentips now is to call 124 | `new Opentip(element, content, title, options)`, or with the framework of 125 | your choice (eg, [ender]: `$("#my-div").opentip(content, title options)`) 126 | 127 | - The instantiation of new tips inside an event (eg: `onclick`, `onmouseover`) 128 | is no longer supported! This would create new opentips everytime the event 129 | is fired. 130 | 131 | - `Opentip.debugging = true;` does no longer exist. Use `Opentip.debug = true;` 132 | 133 | - Positions are no longer of the weird form `[ "left", "top" ]` but simply 134 | strings like `"top left"` or `"right"` 135 | 136 | - `stem.size` has been dropped in favor of `stem.length` and `stem.base` 137 | 138 | - Most of the design is now done in JS since the whole thing is a canvas now. 139 | 140 | - The way close buttons are defined has completely changed. Please refer to the 141 | docs for more information. 142 | 143 | Tagging 144 | ------- 145 | 146 | Tagging in this project is done with my [tag script](http://github.com/enyo/tag). 147 | 148 | 149 | Authors 150 | ------- 151 | 152 | Opentip is written by Matias Meno.
153 | Original graphics by Tjandra Mayerhold. 154 | 155 | ### Contributors 156 | 157 | Thanks to the following people for providing bug reports, feature requests and fixes: 158 | 159 | - Torsten Saam 160 | - Aaron Peckham 161 | - Oguri 162 | - MaxKirillov 163 | - Nick Daugherty 164 | 165 | If I forgot somebody, please just tell me. 166 | 167 | ### Related projects 168 | 169 | You might also be interested in my [formwatcher](http://www.formwatcher.org/) or 170 | [dropzone](http://www.dropzonejs.com/). 171 | 172 | License 173 | ------- 174 | (The MIT License) 175 | 176 | Copyright (c) 2012 Matias Meno <m@tias.me>
177 | 178 | Permission is hereby granted, free of charge, to any person obtaining a copy of 179 | this software and associated documentation files (the "Software"), to deal in 180 | the Software without restriction, including without limitation the rights to 181 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 182 | of the Software, and to permit persons to whom the Software is furnished to do 183 | so, subject to the following conditions: 184 | 185 | The above copyright notice and this permission notice shall be included in all 186 | copies or substantial portions of the Software. 187 | 188 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 189 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 190 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 191 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 192 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 193 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 194 | SOFTWARE. 195 | 196 | [opentip]: http://www.opentip.org/ 197 | [prototype]: http://www.prototypejs.org/ 198 | [jquery]: http://jquery.com/ 199 | [ajax]: http://en.wikipedia.org/wiki/Ajax_(programming) 200 | [excanvas]: https://github.com/enyo/excanvas 201 | [ender]: http://ender.no.de 202 | [component]: https://github.com/component -------------------------------------------------------------------------------- /test/assets/js/tests/050.opentip.draw.coffee: -------------------------------------------------------------------------------- 1 | 2 | $ = jQuery 3 | 4 | describe "Opentip - Drawing", -> 5 | adapter = Opentip.adapter 6 | opentip = null 7 | 8 | 9 | afterEach -> 10 | opentip[prop]?.restore?() for own prop of opentip 11 | opentip?.deactivate?() 12 | $(".opentip-container").remove() 13 | 14 | describe "_draw()", -> 15 | beforeEach -> 16 | opentip = new Opentip adapter.create("
"), "Test", delay: 0 17 | sinon.stub opentip, "_triggerElementExists", -> yes 18 | 19 | it "should abort if @redraw not set", -> 20 | sinon.stub opentip, "debug" 21 | opentip.backgroundCanvas = document.createElement "canvas" 22 | opentip.redraw = off 23 | opentip._draw() 24 | expect(opentip.debug.callCount).to.be 0 25 | 26 | it "should abort if no canvas not set", -> 27 | sinon.stub opentip, "debug" 28 | opentip.redraw = on 29 | opentip._draw() 30 | expect(opentip.debug.callCount).to.be 0 31 | 32 | it "should draw if canvas and @redraw", -> 33 | sinon.stub opentip, "debug" 34 | opentip.backgroundCanvas = document.createElement "canvas" 35 | opentip.redraw = on 36 | opentip._draw() 37 | expect(opentip.debug.callCount).to.be.above 0 38 | expect(opentip.debug.args[0][0]).to.be "Drawing background." 39 | 40 | it "should add the stem classes", -> 41 | sinon.stub opentip, "debug" 42 | opentip.backgroundCanvas = document.createElement "canvas" 43 | 44 | opentip.currentStem = new Opentip.Joint "bottom left" 45 | opentip.redraw = on 46 | opentip._draw() 47 | 48 | unwrappedContainer = Opentip.adapter.unwrap(opentip.container) 49 | expect(unwrappedContainer.classList.contains("stem-bottom")).to.be.ok() 50 | expect(unwrappedContainer.classList.contains("stem-left")).to.be.ok() 51 | 52 | opentip.currentStem = new Opentip.Joint "right middle" 53 | opentip.redraw = on 54 | opentip._draw() 55 | 56 | expect(unwrappedContainer.classList.contains("stem-bottom")).not.to.be.ok() 57 | expect(unwrappedContainer.classList.contains("stem-left")).not.to.be.ok() 58 | expect(unwrappedContainer.classList.contains("stem-middle")).to.be.ok() 59 | expect(unwrappedContainer.classList.contains("stem-right")).to.be.ok() 60 | 61 | it "should set the correct width of the canvas" 62 | it "should set the correct offset of the canvas" 63 | 64 | describe "with close button", -> 65 | options = { } 66 | element = null 67 | beforeEach -> 68 | element = $("
") 69 | $(document.body).append(element) 70 | sinon.stub Opentip.adapter, "dimensions", -> { width: 199, height: 100 } # -1 because of the firefox bug 71 | options = 72 | delay: 0 73 | stem: no 74 | hideTrigger: "closeButton" 75 | closeButtonRadius: 20 76 | closeButtonOffset: [ 0, 10 ] 77 | closeButtonCrossSize: 10 78 | closeButtonLinkOverscan: 5 79 | borderWidth: 0 80 | containInViewport: no 81 | 82 | afterEach -> 83 | element.remove() 84 | Opentip.adapter.dimensions.restore() 85 | 86 | createAndShowTooltip = -> 87 | opentip = new Opentip element.get(0), "Test", options 88 | # opentip._storeAndLockDimensions = -> @dimensions = { width: 200, height: 100 } 89 | # opentip._ensureViewportContainment = (e, position) -> 90 | # { 91 | # position: position 92 | # stem: @options.stem 93 | # } 94 | sinon.stub opentip, "_triggerElementExists", -> yes 95 | opentip.show() 96 | expect(opentip._dimensionsEqual opentip.dimensions, { width: 200, height: 100 }).to.be.ok() 97 | opentip 98 | 99 | 100 | it "should position the close link when no border", -> 101 | options.borderWidth = 0 102 | options.closeButtonOffset = [ 0, 10 ] 103 | 104 | createAndShowTooltip() 105 | 106 | el = adapter.unwrap opentip.closeButtonElement 107 | 108 | expect(el.style.left).to.be "190px" 109 | expect(el.style.top).to.be "0px" 110 | expect(el.style.width).to.be "20px" # cross size + overscan*2 111 | expect(el.style.height).to.be "20px" 112 | 113 | it "should position the close link when border and different overscan", -> 114 | options.borderWidth = 1 115 | options.closeButtonLinkOverscan = 10 116 | 117 | createAndShowTooltip() 118 | 119 | el = adapter.unwrap opentip.closeButtonElement 120 | 121 | expect(el.style.left).to.be "185px" 122 | expect(el.style.top).to.be "-5px" 123 | expect(el.style.width).to.be "30px" # cross size + overscan*2 124 | expect(el.style.height).to.be "30px" 125 | 126 | it "should position the close link with different offsets and overscans", -> 127 | options.closeButtonOffset = [ 10, 5 ] 128 | options.closeButtonCrossSize = 10 129 | options.closeButtonLinkOverscan = 0 130 | 131 | createAndShowTooltip() 132 | 133 | el = adapter.unwrap opentip.closeButtonElement 134 | 135 | expect(el.style.left).to.be "185px" 136 | expect(el.style.top).to.be "0px" 137 | expect(el.style.width).to.be "10px" # cross size + overscan*2 138 | expect(el.style.height).to.be "10px" 139 | 140 | it "should correctly position the close link on the left when stem on top right", -> 141 | options.closeButtonOffset = [ 20, 17 ] 142 | options.closeButtonCrossSize = 12 143 | options.closeButtonLinkOverscan = 5 144 | options.stem = "top right" 145 | 146 | opentip = createAndShowTooltip() 147 | 148 | el = adapter.unwrap opentip.closeButtonElement 149 | 150 | expect(opentip.options.stem.toString()).to.be "top right" 151 | 152 | expect(el.style.left).to.be "9px" 153 | expect(el.style.top).to.be "6px" 154 | expect(el.style.width).to.be "22px" # cross size + overscan*2 155 | expect(el.style.height).to.be "22px" 156 | 157 | 158 | describe "_getPathStemMeasures()", -> 159 | it "should just return the same measures if borderWidth is 0", -> 160 | {stemBase, stemLength} = opentip._getPathStemMeasures 6, 10, 0 161 | expect(stemBase).to.be 6 162 | expect(stemLength).to.be 10 163 | it "should properly calculate the pathStem information if borderWidth > 0", -> 164 | {stemBase, stemLength} = opentip._getPathStemMeasures 6, 10, 3 165 | expect(stemBase).to.be 3.767908047326835 166 | expect(stemLength).to.be 6.2798467455447256 167 | it "should throw an exception if the measures aren't right", -> 168 | expect(-> opentip._getPathStemMeasures 6, 10, 40).to.throwError() 169 | 170 | describe "_getColor()", -> 171 | dimensions = width: 200, height: 100 172 | 173 | cavans = document.createElement "canvas" 174 | ctx = cavans.getContext "2d" 175 | gradient = ctx.createLinearGradient 0, 0, 1, 1 176 | 177 | ctx = sinon.stub ctx 178 | 179 | gradient = sinon.stub gradient 180 | ctx.createLinearGradient.returns gradient 181 | 182 | it "should just return the hex color", -> 183 | expect(Opentip::_getColor ctx, dimensions, "#f00").to.be "#f00" 184 | 185 | it "should just return rgba color", -> 186 | expect(Opentip::_getColor ctx, dimensions, "rgba(0, 0, 0, 0.3)").to.be "rgba(0, 0, 0, 0.3)" 187 | 188 | it "should just return named color", -> 189 | expect(Opentip::_getColor ctx, dimensions, "red").to.be "red" 190 | 191 | it "should create and return gradient", -> 192 | color = Opentip::_getColor ctx, dimensions, [ [0, "black"], [1, "white"] ] 193 | expect(gradient.addColorStop.callCount).to.be 2 194 | expect(color).to.be gradient 195 | 196 | 197 | -------------------------------------------------------------------------------- /docs/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Layout and Typography ----------------------------*/ 2 | body { 3 | font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 4 | font-size: 15px; 5 | line-height: 22px; 6 | color: #252519; 7 | margin: 0; padding: 0; 8 | } 9 | a { 10 | color: #261a3b; 11 | } 12 | a:visited { 13 | color: #261a3b; 14 | } 15 | p { 16 | margin: 0 0 15px 0; 17 | } 18 | h1, h2, h3, h4, h5, h6 { 19 | margin: 0px 0 15px 0; 20 | } 21 | h1 { 22 | margin-top: 40px; 23 | } 24 | #container { 25 | position: relative; 26 | } 27 | #background { 28 | position: fixed; 29 | top: 0; left: 525px; right: 0; bottom: 0; 30 | background: #f5f5ff; 31 | border-left: 1px solid #e5e5ee; 32 | z-index: -1; 33 | } 34 | #jump_to, #jump_page { 35 | background: white; 36 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 37 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 38 | font: 10px Arial; 39 | text-transform: uppercase; 40 | cursor: pointer; 41 | text-align: right; 42 | } 43 | #jump_to, #jump_wrapper { 44 | position: fixed; 45 | right: 0; top: 0; 46 | padding: 5px 10px; 47 | } 48 | #jump_wrapper { 49 | padding: 0; 50 | display: none; 51 | } 52 | #jump_to:hover #jump_wrapper { 53 | display: block; 54 | } 55 | #jump_page { 56 | padding: 5px 0 3px; 57 | margin: 0 0 25px 25px; 58 | } 59 | #jump_page .source { 60 | display: block; 61 | padding: 5px 10px; 62 | text-decoration: none; 63 | border-top: 1px solid #eee; 64 | } 65 | #jump_page .source:hover { 66 | background: #f5f5ff; 67 | } 68 | #jump_page .source:first-child { 69 | } 70 | table td { 71 | border: 0; 72 | outline: 0; 73 | } 74 | td.docs, th.docs { 75 | max-width: 450px; 76 | min-width: 450px; 77 | min-height: 5px; 78 | padding: 10px 25px 1px 50px; 79 | overflow-x: hidden; 80 | vertical-align: top; 81 | text-align: left; 82 | } 83 | .docs pre { 84 | margin: 15px 0 15px; 85 | padding-left: 15px; 86 | } 87 | .docs p tt, .docs p code { 88 | background: #f8f8ff; 89 | border: 1px solid #dedede; 90 | font-size: 12px; 91 | padding: 0 0.2em; 92 | } 93 | .pilwrap { 94 | position: relative; 95 | } 96 | .pilcrow { 97 | font: 12px Arial; 98 | text-decoration: none; 99 | color: #454545; 100 | position: absolute; 101 | top: 3px; left: -20px; 102 | padding: 1px 2px; 103 | opacity: 0; 104 | -webkit-transition: opacity 0.2s linear; 105 | } 106 | td.docs:hover .pilcrow { 107 | opacity: 1; 108 | } 109 | td.code, th.code { 110 | padding: 14px 15px 16px 25px; 111 | width: 100%; 112 | vertical-align: top; 113 | background: #f5f5ff; 114 | border-left: 1px solid #e5e5ee; 115 | } 116 | pre, tt, code { 117 | font-size: 12px; line-height: 18px; 118 | font-family: Monaco, Consolas, "Lucida Console", monospace; 119 | margin: 0; padding: 0; 120 | } 121 | 122 | 123 | /*---------------------- Syntax Highlighting -----------------------------*/ 124 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 125 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 126 | body .hll { background-color: #ffffcc } 127 | body .c { color: #408080; font-style: italic } /* Comment */ 128 | body .err { border: 1px solid #FF0000 } /* Error */ 129 | body .k { color: #954121 } /* Keyword */ 130 | body .o { color: #666666 } /* Operator */ 131 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 132 | body .cp { color: #BC7A00 } /* Comment.Preproc */ 133 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */ 134 | body .cs { color: #408080; font-style: italic } /* Comment.Special */ 135 | body .gd { color: #A00000 } /* Generic.Deleted */ 136 | body .ge { font-style: italic } /* Generic.Emph */ 137 | body .gr { color: #FF0000 } /* Generic.Error */ 138 | body .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 139 | body .gi { color: #00A000 } /* Generic.Inserted */ 140 | body .go { color: #808080 } /* Generic.Output */ 141 | body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 142 | body .gs { font-weight: bold } /* Generic.Strong */ 143 | body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 144 | body .gt { color: #0040D0 } /* Generic.Traceback */ 145 | body .kc { color: #954121 } /* Keyword.Constant */ 146 | body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */ 147 | body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */ 148 | body .kp { color: #954121 } /* Keyword.Pseudo */ 149 | body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */ 150 | body .kt { color: #B00040 } /* Keyword.Type */ 151 | body .m { color: #666666 } /* Literal.Number */ 152 | body .s { color: #219161 } /* Literal.String */ 153 | body .na { color: #7D9029 } /* Name.Attribute */ 154 | body .nb { color: #954121 } /* Name.Builtin */ 155 | body .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 156 | body .no { color: #880000 } /* Name.Constant */ 157 | body .nd { color: #AA22FF } /* Name.Decorator */ 158 | body .ni { color: #999999; font-weight: bold } /* Name.Entity */ 159 | body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 160 | body .nf { color: #0000FF } /* Name.Function */ 161 | body .nl { color: #A0A000 } /* Name.Label */ 162 | body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 163 | body .nt { color: #954121; font-weight: bold } /* Name.Tag */ 164 | body .nv { color: #19469D } /* Name.Variable */ 165 | body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 166 | body .w { color: #bbbbbb } /* Text.Whitespace */ 167 | body .mf { color: #666666 } /* Literal.Number.Float */ 168 | body .mh { color: #666666 } /* Literal.Number.Hex */ 169 | body .mi { color: #666666 } /* Literal.Number.Integer */ 170 | body .mo { color: #666666 } /* Literal.Number.Oct */ 171 | body .sb { color: #219161 } /* Literal.String.Backtick */ 172 | body .sc { color: #219161 } /* Literal.String.Char */ 173 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */ 174 | body .s2 { color: #219161 } /* Literal.String.Double */ 175 | body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 176 | body .sh { color: #219161 } /* Literal.String.Heredoc */ 177 | body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 178 | body .sx { color: #954121 } /* Literal.String.Other */ 179 | body .sr { color: #BB6688 } /* Literal.String.Regex */ 180 | body .s1 { color: #219161 } /* Literal.String.Single */ 181 | body .ss { color: #19469D } /* Literal.String.Symbol */ 182 | body .bp { color: #954121 } /* Name.Builtin.Pseudo */ 183 | body .vc { color: #19469D } /* Name.Variable.Class */ 184 | body .vg { color: #19469D } /* Name.Variable.Global */ 185 | body .vi { color: #19469D } /* Name.Variable.Instance */ 186 | body .il { color: #666666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /src/adapter.native.coffee: -------------------------------------------------------------------------------- 1 | 2 | # Native Opentip Adapter 3 | # ====================== 4 | # 5 | # Use this adapter if you don't use a framework like jQuery and you don't 6 | # really care about oldschool browser compatibility. 7 | class Adapter 8 | 9 | name: "native" 10 | 11 | # Invoke callback as soon as dom is ready 12 | # Source: https://github.com/dperini/ContentLoaded/blob/master/src/contentloaded.js 13 | domReady: (callback) -> 14 | done = no 15 | top = true 16 | win = window 17 | doc = document 18 | 19 | return callback() if doc.readyState in [ "complete", "loaded" ] 20 | 21 | root = doc.documentElement 22 | add = (if doc.addEventListener then "addEventListener" else "attachEvent") 23 | rem = (if doc.addEventListener then "removeEventListener" else "detachEvent") 24 | pre = (if doc.addEventListener then "" else "on") 25 | 26 | init = (e) -> 27 | return if e.type is "readystatechange" and doc.readyState isnt "complete" 28 | (if e.type is "load" then win else doc)[rem] pre + e.type, init, false 29 | unless done 30 | done = yes 31 | callback() 32 | 33 | poll = -> 34 | try 35 | root.doScroll "left" 36 | catch e 37 | setTimeout poll, 50 38 | return 39 | init "poll" 40 | 41 | unless doc.readyState is "complete" 42 | if doc.createEventObject and root.doScroll 43 | try 44 | top = not win.frameElement 45 | poll() if top 46 | doc[add] pre + "DOMContentLoaded", init, false 47 | doc[add] pre + "readystatechange", init, false 48 | win[add] pre + "load", init, false 49 | 50 | 51 | # DOM 52 | # === 53 | 54 | 55 | # Create the HTML passed as string 56 | create: (htmlString) -> 57 | div = document.createElement "div" 58 | div.innerHTML = htmlString 59 | @wrap div.childNodes 60 | 61 | 62 | 63 | # Element handling 64 | # ---------------- 65 | 66 | # Wrap the element in the framework 67 | wrap: (element) -> 68 | if !element 69 | element = [ ] 70 | else if element instanceof NodeList 71 | element = (el for el in element) 72 | else if element not instanceof Array 73 | element = [ element ] 74 | element 75 | 76 | # Returns the unwrapped element 77 | unwrap: (element) -> @wrap(element)[0] 78 | 79 | # Returns the tag name of the element 80 | tagName: (element) -> @unwrap(element).tagName 81 | 82 | # Returns or sets the given attribute of element 83 | attr: (element, attr, value) -> 84 | if arguments.length == 3 85 | @unwrap(element).setAttribute attr, value 86 | else 87 | @unwrap(element).getAttribute attr 88 | 89 | 90 | lastDataId = 0 91 | dataValues = { } 92 | # Returns or sets the given data of element 93 | data: (element, name, value) -> 94 | dataId = @attr element, "data-id" 95 | unless dataId 96 | dataId = ++lastDataId 97 | @attr element, "data-id", dataId 98 | dataValues[dataId] = { } 99 | 100 | if arguments.length == 3 101 | # Setter 102 | dataValues[dataId][name] = value 103 | else 104 | value = dataValues[dataId][name] 105 | return value if value? 106 | 107 | value = @attr element, "data-#{Opentip::dasherize name}" 108 | if value 109 | dataValues[dataId][name] = value 110 | return value 111 | 112 | 113 | 114 | # Finds elements by selector 115 | find: (element, selector) -> @unwrap(element).querySelector selector 116 | 117 | # Finds all elements by selector 118 | findAll: (element, selector) -> @unwrap(element).querySelectorAll selector 119 | 120 | # Updates the content of the element 121 | update: (element, content, escape) -> 122 | element = @unwrap element 123 | if escape 124 | element.innerHTML = "" # Clearing the content 125 | element.appendChild document.createTextNode content 126 | else 127 | element.innerHTML = content 128 | 129 | # Appends given child to element 130 | append: (element, child) -> 131 | unwrappedChild = @unwrap child 132 | unwrappedElement = @unwrap element 133 | unwrappedElement.appendChild unwrappedChild 134 | 135 | # Add a class 136 | addClass: (element, className) -> @unwrap(element).classList.add className 137 | 138 | # Remove a class 139 | removeClass: (element, className) -> @unwrap(element).classList.remove className 140 | 141 | # Set given css properties 142 | css: (element, properties) -> 143 | element = @unwrap @wrap element 144 | for own key, value of properties 145 | element.style[key] = value 146 | 147 | # Returns an object with given dimensions 148 | dimensions: (element) -> 149 | element = @unwrap @wrap element 150 | dimensions = 151 | width: element.offsetWidth 152 | height: element.offsetHeight 153 | 154 | unless dimensions.width and dimensions.height 155 | # The element is probably invisible. So make it visible 156 | revert = 157 | position: element.style.position || '' 158 | visibility: element.style.visibility || '' 159 | display: element.style.display || '' 160 | 161 | @css element, 162 | position: "absolute" 163 | visibility: "hidden" 164 | display: "block" 165 | 166 | dimensions = 167 | width: element.offsetWidth 168 | height: element.offsetHeight 169 | 170 | @css element, revert 171 | 172 | dimensions 173 | 174 | # Returns the scroll offsets of current document 175 | scrollOffset: -> 176 | [ 177 | window.pageXOffset or document.documentElement.scrollLeft or document.body.scrollLeft 178 | window.pageYOffset or document.documentElement.scrollTop or document.body.scrollTop 179 | ] 180 | 181 | # Returns the dimensions of the viewport (currently visible browser area) 182 | viewportDimensions: -> 183 | { 184 | width: document.documentElement.clientWidth 185 | height: document.documentElement.clientHeight 186 | } 187 | 188 | # Returns an object with x and y 189 | mousePosition: (e) -> 190 | pos = x: 0, y: 0 191 | 192 | e ?= window.event 193 | 194 | return unless e? 195 | 196 | try 197 | if e.pageX or e.pageY 198 | pos.x = e.pageX 199 | pos.y = e.pageY 200 | else if e.clientX or e.clientY 201 | pos.x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft 202 | pos.y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop 203 | catch e 204 | pos 205 | 206 | # Returns the offset of the element 207 | offset: (element) -> 208 | element = @unwrap element 209 | 210 | offset = { 211 | top: element.offsetTop 212 | left: element.offsetLeft 213 | } 214 | 215 | while element = element.offsetParent 216 | offset.top += element.offsetTop 217 | offset.left += element.offsetLeft 218 | 219 | if element != document.body 220 | offset.top -= element.scrollTop 221 | offset.left -= element.scrollLeft 222 | 223 | offset 224 | 225 | # Observe given eventName 226 | observe: (element, eventName, observer) -> 227 | # Firefox <= 3.6 needs the last optional parameter `useCapture` 228 | @unwrap(element).addEventListener eventName, observer, false 229 | 230 | # Stop observing event 231 | stopObserving: (element, eventName, observer) -> 232 | # Firefox <= 3.6 needs the last optional parameter `useCapture` 233 | @unwrap(element).removeEventListener eventName, observer, false 234 | 235 | 236 | # Perform an AJAX request and call the appropriate callbacks. 237 | ajax: (options) -> 238 | throw new Error "No url provided" unless options.url? 239 | 240 | if window.XMLHttpRequest 241 | # Mozilla, Safari, ... 242 | request = new XMLHttpRequest 243 | else if window.ActiveXObject 244 | # IE 245 | try 246 | request = new ActiveXObject "Msxml2.XMLHTTP" 247 | catch e 248 | try 249 | request = new ActiveXObject "Microsoft.XMLHTTP" 250 | catch e 251 | 252 | throw new Error "Can't create XMLHttpRequest" unless request 253 | 254 | request.onreadystatechange = -> 255 | if request.readyState == 4 256 | try 257 | if request.status == 200 258 | options.onSuccess? request.responseText 259 | else 260 | options.onError? "Server responded with status #{request.status}" 261 | catch e 262 | options.onError? e.message 263 | 264 | options.onComplete?() 265 | 266 | 267 | request.open options.method?.toUpperCase() ? "GET", options.url 268 | request.send() 269 | 270 | # Utility functions 271 | # ================= 272 | 273 | # Creates a shallow copy of the object 274 | clone: (object) -> 275 | newObject = { } 276 | for own key, val of object 277 | newObject[key] = val 278 | newObject 279 | 280 | # Copies all properties from sources to target 281 | extend: (target, sources...) -> 282 | for source in sources 283 | for own key, val of source 284 | target[key] = val 285 | target 286 | 287 | 288 | 289 | 290 | 291 | 292 | # Add the adapter to the list 293 | Opentip.addAdapter new Adapter 294 | -------------------------------------------------------------------------------- /css/opentip.css: -------------------------------------------------------------------------------- 1 | .opentip-container, 2 | .opentip-container * { 3 | -webkit-box-sizing: border-box; 4 | -moz-box-sizing: border-box; 5 | box-sizing: border-box; 6 | } 7 | .opentip-container { 8 | position: absolute; 9 | max-width: 300px; 10 | z-index: 100; 11 | -webkit-transition: -webkit-transform 1s ease-in-out; 12 | -moz-transition: -moz-transform 1s ease-in-out; 13 | -o-transition: -o-transform 1s ease-in-out; 14 | -ms-transition: -ms-transform 1s ease-in-out; 15 | transition: transform 1s ease-in-out; 16 | pointer-events: none; 17 | -webkit-transform: translateX(0) translateY(0); 18 | -moz-transform: translateX(0) translateY(0); 19 | -o-transform: translateX(0) translateY(0); 20 | -ms-transform: translateX(0) translateY(0); 21 | transform: translateX(0) translateY(0); 22 | } 23 | .opentip-container.fixed.hidden.stem-top.stem-center, 24 | .opentip-container.fixed.going-to-show.stem-top.stem-center, 25 | .opentip-container.fixed.hiding.stem-top.stem-center { 26 | -webkit-transform: translateY(-5px); 27 | -moz-transform: translateY(-5px); 28 | -o-transform: translateY(-5px); 29 | -ms-transform: translateY(-5px); 30 | transform: translateY(-5px); 31 | } 32 | .opentip-container.fixed.hidden.stem-top.stem-right, 33 | .opentip-container.fixed.going-to-show.stem-top.stem-right, 34 | .opentip-container.fixed.hiding.stem-top.stem-right { 35 | -webkit-transform: translateY(-5px) translateX(5px); 36 | -moz-transform: translateY(-5px) translateX(5px); 37 | -o-transform: translateY(-5px) translateX(5px); 38 | -ms-transform: translateY(-5px) translateX(5px); 39 | transform: translateY(-5px) translateX(5px); 40 | } 41 | .opentip-container.fixed.hidden.stem-middle.stem-right, 42 | .opentip-container.fixed.going-to-show.stem-middle.stem-right, 43 | .opentip-container.fixed.hiding.stem-middle.stem-right { 44 | -webkit-transform: translateX(5px); 45 | -moz-transform: translateX(5px); 46 | -o-transform: translateX(5px); 47 | -ms-transform: translateX(5px); 48 | transform: translateX(5px); 49 | } 50 | .opentip-container.fixed.hidden.stem-bottom.stem-right, 51 | .opentip-container.fixed.going-to-show.stem-bottom.stem-right, 52 | .opentip-container.fixed.hiding.stem-bottom.stem-right { 53 | -webkit-transform: translateY(5px) translateX(5px); 54 | -moz-transform: translateY(5px) translateX(5px); 55 | -o-transform: translateY(5px) translateX(5px); 56 | -ms-transform: translateY(5px) translateX(5px); 57 | transform: translateY(5px) translateX(5px); 58 | } 59 | .opentip-container.fixed.hidden.stem-bottom.stem-center, 60 | .opentip-container.fixed.going-to-show.stem-bottom.stem-center, 61 | .opentip-container.fixed.hiding.stem-bottom.stem-center { 62 | -webkit-transform: translateY(5px); 63 | -moz-transform: translateY(5px); 64 | -o-transform: translateY(5px); 65 | -ms-transform: translateY(5px); 66 | transform: translateY(5px); 67 | } 68 | .opentip-container.fixed.hidden.stem-bottom.stem-left, 69 | .opentip-container.fixed.going-to-show.stem-bottom.stem-left, 70 | .opentip-container.fixed.hiding.stem-bottom.stem-left { 71 | -webkit-transform: translateY(5px) translateX(-5px); 72 | -moz-transform: translateY(5px) translateX(-5px); 73 | -o-transform: translateY(5px) translateX(-5px); 74 | -ms-transform: translateY(5px) translateX(-5px); 75 | transform: translateY(5px) translateX(-5px); 76 | } 77 | .opentip-container.fixed.hidden.stem-middle.stem-left, 78 | .opentip-container.fixed.going-to-show.stem-middle.stem-left, 79 | .opentip-container.fixed.hiding.stem-middle.stem-left { 80 | -webkit-transform: translateX(-5px); 81 | -moz-transform: translateX(-5px); 82 | -o-transform: translateX(-5px); 83 | -ms-transform: translateX(-5px); 84 | transform: translateX(-5px); 85 | } 86 | .opentip-container.fixed.hidden.stem-top.stem-left, 87 | .opentip-container.fixed.going-to-show.stem-top.stem-left, 88 | .opentip-container.fixed.hiding.stem-top.stem-left { 89 | -webkit-transform: translateY(-5px) translateX(-5px); 90 | -moz-transform: translateY(-5px) translateX(-5px); 91 | -o-transform: translateY(-5px) translateX(-5px); 92 | -ms-transform: translateY(-5px) translateX(-5px); 93 | transform: translateY(-5px) translateX(-5px); 94 | } 95 | .opentip-container.fixed .opentip { 96 | pointer-events: auto; 97 | } 98 | .opentip-container.hidden { 99 | display: none; 100 | } 101 | .opentip-container .opentip { 102 | position: relative; 103 | font-size: 13px; 104 | line-height: 120%; 105 | padding: 9px 14px; 106 | color: #4f4b47; 107 | text-shadow: -1px -1px 0px rgba(255,255,255,0.2); 108 | } 109 | .opentip-container .opentip .header { 110 | margin: 0; 111 | padding: 0; 112 | } 113 | .opentip-container .opentip .close { 114 | pointer-events: auto; 115 | display: block; 116 | position: absolute; 117 | top: -12px; 118 | left: 60px; 119 | color: rgba(0,0,0,0.5); 120 | background: rgba(0,0,0,0); 121 | text-decoration: none; 122 | } 123 | .opentip-container .opentip .close span { 124 | display: none; 125 | } 126 | .opentip-container .opentip .loading-indicator { 127 | display: none; 128 | } 129 | .opentip-container.loading .loading-indicator { 130 | width: 30px; 131 | height: 30px; 132 | font-size: 30px; 133 | line-height: 30px; 134 | font-weight: bold; 135 | display: block; 136 | } 137 | .opentip-container.loading .loading-indicator span { 138 | display: block; 139 | -webkit-animation: loading 2s linear infinite; 140 | -moz-animation: loading 2s linear infinite; 141 | -o-animation: loading 2s linear infinite; 142 | -ms-animation: loading 2s linear infinite; 143 | animation: loading 2s linear infinite; 144 | text-align: center; 145 | } 146 | .opentip-container.style-dark .opentip, 147 | .opentip-container.style-alert .opentip { 148 | color: #f8f8f8; 149 | text-shadow: 1px 1px 0px rgba(0,0,0,0.2); 150 | } 151 | .opentip-container.style-glass .opentip { 152 | padding: 15px 25px; 153 | color: #317cc5; 154 | text-shadow: 1px 1px 8px rgba(0,94,153,0.3); 155 | } 156 | .opentip-container.hide-effect-fade { 157 | -webkit-transition: -webkit-transform 0.5s ease-in-out, opacity 1s ease-in-out; 158 | -moz-transition: -moz-transform 0.5s ease-in-out, opacity 1s ease-in-out; 159 | -o-transition: -o-transform 0.5s ease-in-out, opacity 1s ease-in-out; 160 | -ms-transition: -ms-transform 0.5s ease-in-out, opacity 1s ease-in-out; 161 | transition: transform 0.5s ease-in-out, opacity 1s ease-in-out; 162 | opacity: 1; 163 | -ms-filter: none; 164 | filter: none; 165 | } 166 | .opentip-container.hide-effect-fade.hiding { 167 | opacity: 0; 168 | filter: alpha(opacity=0); 169 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 170 | } 171 | .opentip-container.show-effect-appear.going-to-show, 172 | .opentip-container.show-effect-appear.showing { 173 | -webkit-transition: -webkit-transform 0.5s ease-in-out, opacity 1s ease-in-out; 174 | -moz-transition: -moz-transform 0.5s ease-in-out, opacity 1s ease-in-out; 175 | -o-transition: -o-transform 0.5s ease-in-out, opacity 1s ease-in-out; 176 | -ms-transition: -ms-transform 0.5s ease-in-out, opacity 1s ease-in-out; 177 | transition: transform 0.5s ease-in-out, opacity 1s ease-in-out; 178 | } 179 | .opentip-container.show-effect-appear.going-to-show { 180 | opacity: 0; 181 | filter: alpha(opacity=0); 182 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 183 | } 184 | .opentip-container.show-effect-appear.showing { 185 | opacity: 1; 186 | -ms-filter: none; 187 | filter: none; 188 | } 189 | .opentip-container.show-effect-appear.visible { 190 | opacity: 1; 191 | -ms-filter: none; 192 | filter: none; 193 | } 194 | @-moz-keyframes loading { 195 | 0% { 196 | -webkit-transform: rotate(0deg); 197 | -moz-transform: rotate(0deg); 198 | -o-transform: rotate(0deg); 199 | -ms-transform: rotate(0deg); 200 | transform: rotate(0deg); 201 | } 202 | 203 | 100% { 204 | -webkit-transform: rotate(360deg); 205 | -moz-transform: rotate(360deg); 206 | -o-transform: rotate(360deg); 207 | -ms-transform: rotate(360deg); 208 | transform: rotate(360deg); 209 | } 210 | } 211 | @-webkit-keyframes loading { 212 | 0% { 213 | -webkit-transform: rotate(0deg); 214 | -moz-transform: rotate(0deg); 215 | -o-transform: rotate(0deg); 216 | -ms-transform: rotate(0deg); 217 | transform: rotate(0deg); 218 | } 219 | 220 | 100% { 221 | -webkit-transform: rotate(360deg); 222 | -moz-transform: rotate(360deg); 223 | -o-transform: rotate(360deg); 224 | -ms-transform: rotate(360deg); 225 | transform: rotate(360deg); 226 | } 227 | } 228 | @-o-keyframes loading { 229 | 0% { 230 | -webkit-transform: rotate(0deg); 231 | -moz-transform: rotate(0deg); 232 | -o-transform: rotate(0deg); 233 | -ms-transform: rotate(0deg); 234 | transform: rotate(0deg); 235 | } 236 | 237 | 100% { 238 | -webkit-transform: rotate(360deg); 239 | -moz-transform: rotate(360deg); 240 | -o-transform: rotate(360deg); 241 | -ms-transform: rotate(360deg); 242 | transform: rotate(360deg); 243 | } 244 | } 245 | @-ms-keyframes loading { 246 | 0% { 247 | -webkit-transform: rotate(0deg); 248 | -moz-transform: rotate(0deg); 249 | -o-transform: rotate(0deg); 250 | -ms-transform: rotate(0deg); 251 | transform: rotate(0deg); 252 | } 253 | 254 | 100% { 255 | -webkit-transform: rotate(360deg); 256 | -moz-transform: rotate(360deg); 257 | -o-transform: rotate(360deg); 258 | -ms-transform: rotate(360deg); 259 | transform: rotate(360deg); 260 | } 261 | } 262 | @keyframes loading { 263 | 0% { 264 | -webkit-transform: rotate(0deg); 265 | -moz-transform: rotate(0deg); 266 | -o-transform: rotate(0deg); 267 | -ms-transform: rotate(0deg); 268 | transform: rotate(0deg); 269 | } 270 | 271 | 100% { 272 | -webkit-transform: rotate(360deg); 273 | -moz-transform: rotate(360deg); 274 | -o-transform: rotate(360deg); 275 | -ms-transform: rotate(360deg); 276 | transform: rotate(360deg); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /test/assets/js/tests/030.opentip.show.coffee: -------------------------------------------------------------------------------- 1 | 2 | $ = jQuery 3 | 4 | describe "Opentip - Appearing", -> 5 | adapter = Opentip.adapter 6 | opentip = null 7 | triggerElementExists = yes 8 | 9 | beforeEach -> 10 | triggerElementExists = yes 11 | 12 | afterEach -> 13 | opentip[prop]?.restore?() for own prop of opentip 14 | opentip.deactivate() 15 | 16 | $(".opentip-container").remove() 17 | 18 | describe "prepareToShow()", -> 19 | beforeEach -> 20 | triggerElementExists = no 21 | opentip = new Opentip adapter.create("
"), "Test", delay: 0 22 | sinon.stub opentip, "_triggerElementExists", -> triggerElementExists 23 | 24 | it "should always abort a hiding process", -> 25 | sinon.stub opentip, "_abortHiding" 26 | opentip.prepareToShow() 27 | expect(opentip._abortHiding.callCount).to.be 1 28 | 29 | it "even when aborting because it's already visible", -> 30 | sinon.stub opentip, "_abortHiding" 31 | opentip.visible = yes 32 | opentip.prepareToShow() 33 | expect(opentip._abortHiding.callCount).to.be 1 34 | 35 | it "should abort when already visible", -> 36 | expect(opentip.preparingToShow).to.not.be.ok() 37 | opentip.visible = yes 38 | opentip.prepareToShow() 39 | expect(opentip.preparingToShow).to.not.be.ok() 40 | opentip.visible = no 41 | opentip.prepareToShow() 42 | expect(opentip.preparingToShow).to.be.ok() 43 | 44 | it "should log that it's preparing to show", -> 45 | sinon.stub opentip, "debug" 46 | opentip.prepareToShow() 47 | expect(opentip.debug.callCount).to.be 1 48 | 49 | it "should setup observers for 'showing'", -> 50 | sinon.stub opentip, "_setupObservers" 51 | opentip.prepareToShow() 52 | expect(opentip._setupObservers.callCount).to.be 1 53 | expect(opentip._setupObservers.getCall(0).args[0]).to.be "-hidden" 54 | expect(opentip._setupObservers.getCall(0).args[1]).to.be "-hiding" 55 | expect(opentip._setupObservers.getCall(0).args[2]).to.be "showing" 56 | 57 | it "should start following mouseposition", -> 58 | sinon.stub opentip, "_followMousePosition" 59 | opentip.prepareToShow() 60 | expect(opentip._followMousePosition.callCount).to.be 1 61 | 62 | it "should reposition itself «On se redresse!»", -> 63 | sinon.stub opentip, "reposition" 64 | opentip.prepareToShow() 65 | expect(opentip.reposition.callCount).to.be 1 66 | 67 | it "should call show() after the specified delay (50ms)", (done) -> 68 | opentip.options.delay = 0.05 69 | sinon.stub opentip, "show", -> done() 70 | opentip.prepareToShow() 71 | 72 | describe "show()", -> 73 | beforeEach -> 74 | opentip = new Opentip adapter.create("
"), "Test", delay: 0 75 | sinon.stub opentip, "_triggerElementExists", -> triggerElementExists 76 | 77 | it "should clear all timeouts", -> 78 | triggerElementExists = no 79 | sinon.stub opentip, "_clearTimeouts" 80 | opentip.show() 81 | expect(opentip._clearTimeouts.callCount).to.be.above 0 82 | it "should not clear all timeouts when already visible", -> 83 | triggerElementExists = no 84 | sinon.stub opentip, "_clearTimeouts" 85 | opentip.visible = yes 86 | opentip.show() 87 | expect(opentip._clearTimeouts.callCount).to.be 0 88 | 89 | it "should abort if already visible", -> 90 | triggerElementExists = no 91 | sinon.stub opentip, "debug" 92 | opentip.visible = yes 93 | opentip.show() 94 | expect(opentip.debug.callCount).to.be 0 95 | 96 | it "should log that it's showing", -> 97 | sinon.stub opentip, "debug" 98 | opentip.show() 99 | expect(opentip.debug.callCount).to.be.above 1 100 | expect(opentip.debug.args[0][0]).to.be "Showing now." 101 | 102 | it "should set visible to true and preparingToShow to false", -> 103 | opentip.preparingToShow = yes 104 | opentip.show() 105 | expect(opentip.visible).to.be.ok() 106 | expect(opentip.preparingToShow).to.not.be.ok() 107 | 108 | it "should use _ensureViewportContaintment if options.containInViewport", -> 109 | sinon.spy opentip, "_ensureViewportContainment" 110 | sinon.stub opentip, "getPosition", -> x: 0, y: 0 111 | opentip.show() 112 | expect(opentip._ensureViewportContainment.callCount).to.be.above 1 113 | 114 | it "should not use _ensureViewportContaintment if !options.containInViewport", -> 115 | opentip.options.containInViewport = no 116 | sinon.stub opentip, "_ensureViewportContainment" 117 | opentip.show() 118 | expect(opentip._ensureViewportContainment.callCount).to.be 0 119 | 120 | describe "grouped Opentips", -> 121 | it "should hide all other opentips", -> 122 | Opentip.tips = [ ] 123 | opentip = new Opentip adapter.create("
"), "Test", { delay: 0, group: "test" } 124 | opentip2 = new Opentip adapter.create("
"), "Test", { delay: 0, group: "test" } 125 | opentip3 = new Opentip adapter.create("
"), "Test", { delay: 0, group: "test" } 126 | sinon.stub opentip, "_triggerElementExists", -> triggerElementExists 127 | sinon.stub opentip2, "_triggerElementExists", -> triggerElementExists 128 | sinon.stub opentip3, "_triggerElementExists", -> triggerElementExists 129 | 130 | opentip.show() 131 | expect(opentip.visible).to.be.ok() 132 | expect(opentip2.visible).to.not.be.ok() 133 | expect(opentip3.visible).to.not.be.ok() 134 | opentip2.show() 135 | expect(opentip.visible).to.not.be.ok() 136 | expect(opentip2.visible).to.be.ok() 137 | expect(opentip3.visible).to.not.be.ok() 138 | opentip3.show() 139 | expect(opentip.visible).to.not.be.ok() 140 | expect(opentip2.visible).to.not.be.ok() 141 | expect(opentip3.visible).to.be.ok() 142 | 143 | opentip.deactivate() 144 | opentip2.deactivate() 145 | opentip3.deactivate() 146 | 147 | it "should abort showing other opentips", -> 148 | Opentip.tips = [ ] 149 | opentip = new Opentip adapter.create("
"), "Test", { delay: 1000, group: "test" } 150 | opentip2 = new Opentip adapter.create("
"), "Test", { delay: 1000, group: "test" } 151 | opentip3 = new Opentip adapter.create("
"), "Test", { delay: 1000, group: "test" } 152 | sinon.stub opentip, "_triggerElementExists", -> triggerElementExists 153 | sinon.stub opentip2, "_triggerElementExists", -> triggerElementExists 154 | sinon.stub opentip3, "_triggerElementExists", -> triggerElementExists 155 | 156 | opentip.prepareToShow() 157 | expect(opentip.visible).to.not.be.ok() 158 | expect(opentip.preparingToShow).to.be.ok() 159 | expect(opentip2.visible).to.not.be.ok() 160 | expect(opentip2.preparingToShow).to.not.be.ok() 161 | 162 | opentip2.prepareToShow() 163 | expect(opentip.visible).to.not.be.ok() 164 | expect(opentip.preparingToShow).to.not.be.ok() 165 | expect(opentip2.visible).to.not.be.ok() 166 | expect(opentip2.preparingToShow).to.be.ok() 167 | 168 | opentip3.show() 169 | expect(opentip.visible).to.not.be.ok() 170 | expect(opentip.preparingToShow).to.not.be.ok() 171 | expect(opentip2.visible).to.not.be.ok() 172 | expect(opentip2.preparingToShow).to.not.be.ok() 173 | 174 | opentip.deactivate() 175 | opentip2.deactivate() 176 | opentip3.deactivate() 177 | 178 | describe "events", -> 179 | 180 | element = "" 181 | 182 | beforeEach -> 183 | element = document.createElement "div" 184 | 185 | testEvent = (opentip, event, done) -> 186 | expect(opentip.visible).to.not.be.ok() 187 | 188 | Test.triggerEvent element, event 189 | 190 | expect(opentip.preparingToShow).to.be.ok() 191 | expect(opentip.visible).to.not.be.ok() 192 | setTimeout -> 193 | try 194 | expect(opentip.visible).to.be.ok() 195 | done() 196 | catch e 197 | done e 198 | , 2 199 | 200 | for event in [ "click", "mouseover", "focus" ] 201 | it "should show on #{event}", (done) -> 202 | opentip = new Opentip element, "test", delay: 0, showOn: event 203 | sinon.stub opentip, "_triggerElementExists", -> triggerElementExists 204 | testEvent opentip, event, done 205 | 206 | describe "visible", -> 207 | $element = null 208 | element = null 209 | span = null 210 | beforeEach -> 211 | $element = $ "
" 212 | span = $element.find "span" 213 | element = $element[0] 214 | 215 | 216 | # it "should not hide when hovering child elements and hideOn == mouseout", (done) -> 217 | # opentip = new Opentip element, "test", 218 | # delay: 0 219 | # hideDelay: 0 220 | # showOn: "click" 221 | # hideOn: "mouseout" 222 | 223 | # sinon.stub opentip, "_triggerElementExists", -> triggerElementExists 224 | 225 | # expect(opentip.visible).to.not.be.ok() 226 | # enderElement.trigger "click" 227 | # expect(opentip.preparingToShow).to.be.ok() 228 | # expect(opentip.visible).to.not.be.ok() 229 | # setTimeout -> 230 | # try 231 | # expect(opentip.visible).to.be.ok() 232 | # enderElement.trigger "mouseout" 233 | # enderElement.trigger "mouseover" 234 | # setTimeout -> 235 | # try 236 | # expect(opentip.visible).to.be.ok() 237 | # catch e 238 | # done e 239 | # , 4 240 | # done() 241 | # catch e 242 | # done e 243 | # , 4 244 | 245 | it "should activate all hide buttons", (done) -> 246 | closeClass = Opentip::class.close 247 | opentip = new Opentip element, """close""", 248 | escape: no 249 | delay: 0 250 | hideDelay: 0 251 | hideTrigger: "closeButton" 252 | hideOn: "click" 253 | 254 | sinon.stub opentip, "_triggerElementExists", -> triggerElementExists 255 | sinon.stub opentip, "prepareToHide" 256 | 257 | opentip.prepareToShow() 258 | 259 | 260 | setTimeout -> 261 | try 262 | closeButtons = $(opentip.container).find(".#{closeClass}") 263 | expect(closeButtons.length).to.be 2 # The close button created by opentip and the one in content 264 | 265 | expect(opentip.prepareToHide.callCount).to.be 0 266 | catch e 267 | done e 268 | return 269 | 270 | Test.triggerEvent closeButtons[0], "click" 271 | 272 | setTimeout -> 273 | try 274 | expect(opentip.prepareToHide.callCount).to.be 1 275 | done() 276 | catch e 277 | done e 278 | , 4 279 | 4 280 | 281 | 282 | 283 | -------------------------------------------------------------------------------- /lib/adapter.native.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.4.0 2 | var Adapter, 3 | __hasProp = {}.hasOwnProperty, 4 | __slice = [].slice; 5 | 6 | Adapter = (function() { 7 | var dataValues, lastDataId; 8 | 9 | function Adapter() {} 10 | 11 | Adapter.prototype.name = "native"; 12 | 13 | Adapter.prototype.domReady = function(callback) { 14 | var add, doc, done, init, poll, pre, rem, root, top, win, _ref; 15 | done = false; 16 | top = true; 17 | win = window; 18 | doc = document; 19 | if ((_ref = doc.readyState) === "complete" || _ref === "loaded") { 20 | return callback(); 21 | } 22 | root = doc.documentElement; 23 | add = (doc.addEventListener ? "addEventListener" : "attachEvent"); 24 | rem = (doc.addEventListener ? "removeEventListener" : "detachEvent"); 25 | pre = (doc.addEventListener ? "" : "on"); 26 | init = function(e) { 27 | if (e.type === "readystatechange" && doc.readyState !== "complete") { 28 | return; 29 | } 30 | (e.type === "load" ? win : doc)[rem](pre + e.type, init, false); 31 | if (!done) { 32 | done = true; 33 | return callback(); 34 | } 35 | }; 36 | poll = function() { 37 | try { 38 | root.doScroll("left"); 39 | } catch (e) { 40 | setTimeout(poll, 50); 41 | return; 42 | } 43 | return init("poll"); 44 | }; 45 | if (doc.readyState !== "complete") { 46 | if (doc.createEventObject && root.doScroll) { 47 | try { 48 | top = !win.frameElement; 49 | } catch (_error) {} 50 | if (top) { 51 | poll(); 52 | } 53 | } 54 | doc[add](pre + "DOMContentLoaded", init, false); 55 | doc[add](pre + "readystatechange", init, false); 56 | return win[add](pre + "load", init, false); 57 | } 58 | }; 59 | 60 | Adapter.prototype.create = function(htmlString) { 61 | var div; 62 | div = document.createElement("div"); 63 | div.innerHTML = htmlString; 64 | return this.wrap(div.childNodes); 65 | }; 66 | 67 | Adapter.prototype.wrap = function(element) { 68 | var el; 69 | if (!element) { 70 | element = []; 71 | } else if (element instanceof NodeList) { 72 | element = (function() { 73 | var _i, _len, _results; 74 | _results = []; 75 | for (_i = 0, _len = element.length; _i < _len; _i++) { 76 | el = element[_i]; 77 | _results.push(el); 78 | } 79 | return _results; 80 | })(); 81 | } else if (!(element instanceof Array)) { 82 | element = [element]; 83 | } 84 | return element; 85 | }; 86 | 87 | Adapter.prototype.unwrap = function(element) { 88 | return this.wrap(element)[0]; 89 | }; 90 | 91 | Adapter.prototype.tagName = function(element) { 92 | return this.unwrap(element).tagName; 93 | }; 94 | 95 | Adapter.prototype.attr = function(element, attr, value) { 96 | if (arguments.length === 3) { 97 | return this.unwrap(element).setAttribute(attr, value); 98 | } else { 99 | return this.unwrap(element).getAttribute(attr); 100 | } 101 | }; 102 | 103 | lastDataId = 0; 104 | 105 | dataValues = {}; 106 | 107 | Adapter.prototype.data = function(element, name, value) { 108 | var dataId; 109 | dataId = this.attr(element, "data-id"); 110 | if (!dataId) { 111 | dataId = ++lastDataId; 112 | this.attr(element, "data-id", dataId); 113 | dataValues[dataId] = {}; 114 | } 115 | if (arguments.length === 3) { 116 | return dataValues[dataId][name] = value; 117 | } else { 118 | value = dataValues[dataId][name]; 119 | if (value != null) { 120 | return value; 121 | } 122 | value = this.attr(element, "data-" + (Opentip.prototype.dasherize(name))); 123 | if (value) { 124 | dataValues[dataId][name] = value; 125 | } 126 | return value; 127 | } 128 | }; 129 | 130 | Adapter.prototype.find = function(element, selector) { 131 | return this.unwrap(element).querySelector(selector); 132 | }; 133 | 134 | Adapter.prototype.findAll = function(element, selector) { 135 | return this.unwrap(element).querySelectorAll(selector); 136 | }; 137 | 138 | Adapter.prototype.update = function(element, content, escape) { 139 | element = this.unwrap(element); 140 | if (escape) { 141 | element.innerHTML = ""; 142 | return element.appendChild(document.createTextNode(content)); 143 | } else { 144 | return element.innerHTML = content; 145 | } 146 | }; 147 | 148 | Adapter.prototype.append = function(element, child) { 149 | var unwrappedChild, unwrappedElement; 150 | unwrappedChild = this.unwrap(child); 151 | unwrappedElement = this.unwrap(element); 152 | return unwrappedElement.appendChild(unwrappedChild); 153 | }; 154 | 155 | Adapter.prototype.addClass = function(element, className) { 156 | return this.unwrap(element).classList.add(className); 157 | }; 158 | 159 | Adapter.prototype.removeClass = function(element, className) { 160 | return this.unwrap(element).classList.remove(className); 161 | }; 162 | 163 | Adapter.prototype.css = function(element, properties) { 164 | var key, value, _results; 165 | element = this.unwrap(this.wrap(element)); 166 | _results = []; 167 | for (key in properties) { 168 | if (!__hasProp.call(properties, key)) continue; 169 | value = properties[key]; 170 | _results.push(element.style[key] = value); 171 | } 172 | return _results; 173 | }; 174 | 175 | Adapter.prototype.dimensions = function(element) { 176 | var dimensions, revert; 177 | element = this.unwrap(this.wrap(element)); 178 | dimensions = { 179 | width: element.offsetWidth, 180 | height: element.offsetHeight 181 | }; 182 | if (!(dimensions.width && dimensions.height)) { 183 | revert = { 184 | position: element.style.position || '', 185 | visibility: element.style.visibility || '', 186 | display: element.style.display || '' 187 | }; 188 | this.css(element, { 189 | position: "absolute", 190 | visibility: "hidden", 191 | display: "block" 192 | }); 193 | dimensions = { 194 | width: element.offsetWidth, 195 | height: element.offsetHeight 196 | }; 197 | this.css(element, revert); 198 | } 199 | return dimensions; 200 | }; 201 | 202 | Adapter.prototype.scrollOffset = function() { 203 | return [window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop]; 204 | }; 205 | 206 | Adapter.prototype.viewportDimensions = function() { 207 | return { 208 | width: document.documentElement.clientWidth, 209 | height: document.documentElement.clientHeight 210 | }; 211 | }; 212 | 213 | Adapter.prototype.mousePosition = function(e) { 214 | var pos; 215 | pos = { 216 | x: 0, 217 | y: 0 218 | }; 219 | if (e == null) { 220 | e = window.event; 221 | } 222 | if (e == null) { 223 | return; 224 | } 225 | try { 226 | if (e.pageX || e.pageY) { 227 | pos.x = e.pageX; 228 | pos.y = e.pageY; 229 | } else if (e.clientX || e.clientY) { 230 | pos.x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; 231 | pos.y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; 232 | } 233 | } catch (e) { 234 | 235 | } 236 | return pos; 237 | }; 238 | 239 | Adapter.prototype.offset = function(element) { 240 | var offset; 241 | element = this.unwrap(element); 242 | offset = { 243 | top: element.offsetTop, 244 | left: element.offsetLeft 245 | }; 246 | while (element = element.offsetParent) { 247 | offset.top += element.offsetTop; 248 | offset.left += element.offsetLeft; 249 | if (element !== document.body) { 250 | offset.top -= element.scrollTop; 251 | offset.left -= element.scrollLeft; 252 | } 253 | } 254 | return offset; 255 | }; 256 | 257 | Adapter.prototype.observe = function(element, eventName, observer) { 258 | return this.unwrap(element).addEventListener(eventName, observer, false); 259 | }; 260 | 261 | Adapter.prototype.stopObserving = function(element, eventName, observer) { 262 | return this.unwrap(element).removeEventListener(eventName, observer, false); 263 | }; 264 | 265 | Adapter.prototype.ajax = function(options) { 266 | var request, _ref, _ref1; 267 | if (options.url == null) { 268 | throw new Error("No url provided"); 269 | } 270 | if (window.XMLHttpRequest) { 271 | request = new XMLHttpRequest; 272 | } else if (window.ActiveXObject) { 273 | try { 274 | request = new ActiveXObject("Msxml2.XMLHTTP"); 275 | } catch (e) { 276 | try { 277 | request = new ActiveXObject("Microsoft.XMLHTTP"); 278 | } catch (e) { 279 | 280 | } 281 | } 282 | } 283 | if (!request) { 284 | throw new Error("Can't create XMLHttpRequest"); 285 | } 286 | request.onreadystatechange = function() { 287 | if (request.readyState === 4) { 288 | try { 289 | if (request.status === 200) { 290 | if (typeof options.onSuccess === "function") { 291 | options.onSuccess(request.responseText); 292 | } 293 | } else { 294 | if (typeof options.onError === "function") { 295 | options.onError("Server responded with status " + request.status); 296 | } 297 | } 298 | } catch (e) { 299 | if (typeof options.onError === "function") { 300 | options.onError(e.message); 301 | } 302 | } 303 | return typeof options.onComplete === "function" ? options.onComplete() : void 0; 304 | } 305 | }; 306 | request.open((_ref = (_ref1 = options.method) != null ? _ref1.toUpperCase() : void 0) != null ? _ref : "GET", options.url); 307 | return request.send(); 308 | }; 309 | 310 | Adapter.prototype.clone = function(object) { 311 | var key, newObject, val; 312 | newObject = {}; 313 | for (key in object) { 314 | if (!__hasProp.call(object, key)) continue; 315 | val = object[key]; 316 | newObject[key] = val; 317 | } 318 | return newObject; 319 | }; 320 | 321 | Adapter.prototype.extend = function() { 322 | var key, source, sources, target, val, _i, _len; 323 | target = arguments[0], sources = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 324 | for (_i = 0, _len = sources.length; _i < _len; _i++) { 325 | source = sources[_i]; 326 | for (key in source) { 327 | if (!__hasProp.call(source, key)) continue; 328 | val = source[key]; 329 | target[key] = val; 330 | } 331 | } 332 | return target; 333 | }; 334 | 335 | return Adapter; 336 | 337 | })(); 338 | 339 | Opentip.addAdapter(new Adapter); 340 | -------------------------------------------------------------------------------- /test/assets/js/tests/010.opentip.coffee: -------------------------------------------------------------------------------- 1 | 2 | $ = jQuery 3 | 4 | describe "Opentip", -> 5 | adapter = null 6 | beforeEach -> 7 | adapter = Opentip.adapter 8 | 9 | afterEach -> 10 | elements = $(".opentip-container") 11 | elements.remove() 12 | 13 | 14 | describe "constructor()", -> 15 | before -> 16 | sinon.stub Opentip::, "_init" 17 | after -> 18 | Opentip::_init.restore() 19 | it "arguments should be optional", -> 20 | element = adapter.create "
" 21 | opentip = new Opentip element, "content" 22 | expect(opentip.content).to.equal "content" 23 | expect(adapter.unwrap(opentip.triggerElement)).to.equal adapter.unwrap element 24 | 25 | opentip = new Opentip element, "content", "title", { hideOn: "click" } 26 | expect(opentip.content).to.equal "content" 27 | expect(adapter.unwrap opentip.triggerElement).to.equal adapter.unwrap element 28 | expect(opentip.options.hideOn).to.equal "click" 29 | expect(opentip.options.title).to.equal "title" 30 | 31 | opentip = new Opentip element, { hideOn: "click" } 32 | expect(adapter.unwrap opentip.triggerElement).to.equal adapter.unwrap element 33 | expect(opentip.options.hideOn).to.equal "click" 34 | expect(opentip.content).to.equal "" 35 | expect(opentip.options.title).to.equal undefined 36 | 37 | it "should always use the next tip id", -> 38 | element = document.createElement "div" 39 | Opentip.lastId = 0 40 | opentip = new Opentip element, "Test" 41 | opentip2 = new Opentip element, "Test" 42 | opentip3 = new Opentip element, "Test" 43 | expect(opentip.id).to.be 1 44 | expect(opentip2.id).to.be 2 45 | expect(opentip3.id).to.be 3 46 | 47 | it "should use the href attribute if AJAX and an A element", -> 48 | element = $("""link""")[0] 49 | opentip = new Opentip element, ajax: on 50 | expect(opentip.options.ajax).to.equal "http://testlink" 51 | 52 | it "should disable AJAX if neither URL or a link HREF is provided", -> 53 | element = $("""
text
""")[0] 54 | opentip = new Opentip element, ajax: on 55 | expect(opentip.options.ajax).to.be false 56 | 57 | it "should disable a link if the event is onClick", -> 58 | sinon.spy adapter, "observe" 59 | element = $("""link""")[0] 60 | opentip = new Opentip element, showOn: "click" 61 | 62 | expect(adapter.observe.calledOnce).to.be.ok() 63 | expect(adapter.observe.getCall(0).args[1]).to.equal "click" 64 | 65 | adapter.observe.restore() 66 | 67 | it "should take all options from selected style", -> 68 | element = document.createElement "div" 69 | opentip = new Opentip element, style: "glass", showOn: "click" 70 | 71 | # Should have been set by the options 72 | expect(opentip.options.showOn).to.equal "click" 73 | # Should have been set by the glass theme 74 | expect(opentip.options.className).to.equal "glass" 75 | # Should have been set by the standard theme 76 | expect(opentip.options.stemLength).to.equal 5 77 | 78 | it "the property 'style' should be handled the same as 'extends'", -> 79 | element = document.createElement "div" 80 | opentip = new Opentip element, extends: "glass", showOn: "click" 81 | 82 | # Should have been set by the options 83 | expect(opentip.options.showOn).to.equal "click" 84 | # Should have been set by the glass theme 85 | expect(opentip.options.className).to.equal "glass" 86 | # Should have been set by the standard theme 87 | expect(opentip.options.stemLength).to.equal 5 88 | 89 | it "chaining incorrect styles should throw an exception", -> 90 | element = document.createElement "div" 91 | expect(-> new Opentip element, { extends: "invalidstyle" }).to.throwException /Invalid style\: invalidstyle/ 92 | 93 | it "chaining styles should work", -> 94 | element = document.createElement "div" 95 | 96 | Opentip.styles.test1 = stemLength: 40 97 | Opentip.styles.test2 = extends: "test1", title: "overwritten title" 98 | Opentip.styles.test3 = extends: "test2", className: "test5", title: "some title" 99 | 100 | opentip = new Opentip element, { extends: "test3", stemBase: 20 } 101 | 102 | expect(opentip.options.className).to.equal "test5" 103 | expect(opentip.options.title).to.equal "some title" 104 | expect(opentip.options.stemLength).to.equal 40 105 | expect(opentip.options.stemBase).to.equal 20 106 | 107 | it "should set the options to fixed if a target is provided", -> 108 | element = document.createElement "div" 109 | opentip = new Opentip element, target: yes, fixed: no 110 | expect(opentip.options.fixed).to.be.ok() 111 | 112 | it "should use provided stem", -> 113 | element = document.createElement "div" 114 | opentip = new Opentip element, stem: "bottom", tipJoin: "topLeft" 115 | expect(opentip.options.stem.toString()).to.eql "bottom" 116 | 117 | it "should take the tipJoint as stem if stem is just true", -> 118 | element = document.createElement "div" 119 | opentip = new Opentip element, stem: yes, tipJoint: "top left" 120 | expect(opentip.options.stem.toString()).to.eql "top left" 121 | 122 | it "should use provided target", -> 123 | element = adapter.create "
" 124 | element2 = adapter.create "
" 125 | opentip = new Opentip element, target: element2 126 | expect(adapter.unwrap opentip.options.target).to.equal adapter.unwrap element2 127 | 128 | it "should take the triggerElement as target if target is just true", -> 129 | element = adapter.create "
" 130 | opentip = new Opentip element, target: yes 131 | expect(adapter.unwrap opentip.options.target).to.equal adapter.unwrap element 132 | 133 | it "currentStemPosition should be set to inital stemPosition", -> 134 | element = adapter.create "
" 135 | opentip = new Opentip element, stem: "topLeft" 136 | expect(opentip.currentStem.toString()).to.eql "top left" 137 | 138 | it "delay should be automatically set if none provided", -> 139 | element = document.createElement "div" 140 | opentip = new Opentip element, delay: null, showOn: "click" 141 | expect(opentip.options.delay).to.equal 0 142 | opentip = new Opentip element, delay: null, showOn: "mouseover" 143 | expect(opentip.options.delay).to.equal 0.2 144 | 145 | it "the targetJoint should be the inverse of the tipJoint if none provided", -> 146 | element = document.createElement "div" 147 | opentip = new Opentip element, tipJoint: "left" 148 | expect(opentip.options.targetJoint.toString()).to.eql "right" 149 | opentip = new Opentip element, tipJoint: "top" 150 | expect(opentip.options.targetJoint.toString()).to.eql "bottom" 151 | opentip = new Opentip element, tipJoint: "bottom right" 152 | expect(opentip.options.targetJoint.toString()).to.eql "top left" 153 | 154 | 155 | it "should setup all trigger elements", -> 156 | element = adapter.create "
" 157 | opentip = new Opentip element, showOn: "click" 158 | expect(opentip.showTriggers[0].event).to.eql "click" 159 | expect(adapter.unwrap opentip.showTriggers[0].element).to.equal adapter.unwrap element 160 | expect(opentip.showTriggersWhenVisible).to.eql [ ] 161 | expect(opentip.hideTriggers).to.eql [ ] 162 | opentip = new Opentip element, showOn: "creation" 163 | expect(opentip.showTriggers).to.eql [ ] 164 | expect(opentip.showTriggersWhenVisible).to.eql [ ] 165 | expect(opentip.hideTriggers).to.eql [ ] 166 | 167 | it "should copy options.hideTrigger onto options.hideTriggers", -> 168 | element = adapter.create "
" 169 | opentip = new Opentip element, hideTrigger: "closeButton", hideTriggers: [ ] 170 | expect(opentip.options.hideTriggers).to.eql [ "closeButton"] 171 | 172 | it "should NOT copy options.hideTrigger onto options.hideTriggers when hideTriggers are set", -> 173 | element = adapter.create "
" 174 | opentip = new Opentip element, hideTrigger: "closeButton", hideTriggers: [ "tip", "trigger" ] 175 | expect(opentip.options.hideTriggers).to.eql [ "tip", "trigger" ] 176 | 177 | it "should attach itself to the elements `data-opentips` property", -> 178 | element = $("
")[0] 179 | expect(adapter.data element, "opentips").to.not.be.ok() 180 | opentip = new Opentip element 181 | expect(adapter.data element, "opentips").to.eql [ opentip ] 182 | opentip2 = new Opentip element 183 | opentip3 = new Opentip element 184 | expect(adapter.data element, "opentips").to.eql [ opentip, opentip2, opentip3 ] 185 | 186 | it "should add itself to the Opentip.tips list", -> 187 | element = $("
")[0] 188 | Opentip.tips = [ ] 189 | opentip1 = new Opentip element 190 | opentip2 = new Opentip element 191 | expect(Opentip.tips.length).to.equal 2 192 | expect(Opentip.tips[0]).to.equal opentip1 193 | expect(Opentip.tips[1]).to.equal opentip2 194 | 195 | describe "init()", -> 196 | describe "showOn == creation", -> 197 | element = document.createElement "div" 198 | beforeEach -> sinon.stub Opentip::, "prepareToShow" 199 | afterEach -> Opentip::prepareToShow.restore() 200 | it "should immediately call prepareToShow()", -> 201 | opentip = new Opentip element, showOn: "creation" 202 | expect(opentip.prepareToShow.callCount).to.equal 1 203 | 204 | describe "setContent()", -> 205 | it "should update the content if tooltip currently visible", -> 206 | element = document.createElement "div" 207 | opentip = new Opentip element, showOn: "click" 208 | sinon.stub opentip, "_updateElementContent" 209 | opentip.visible = no 210 | opentip.setContent "TEST" 211 | expect(opentip.content).to.equal "TEST" 212 | opentip.visible = yes 213 | opentip.setContent "TEST2" 214 | expect(opentip.content).to.equal "TEST2" 215 | expect(opentip._updateElementContent.callCount).to.equal 1 216 | opentip._updateElementContent.restore() 217 | 218 | 219 | 220 | 221 | describe "_updateElementContent()", -> 222 | 223 | it "should escape the content if @options.escapeContent", -> 224 | element = document.createElement "div" 225 | opentip = new Opentip element, "
", escapeContent: yes 226 | sinon.stub opentip, "_triggerElementExists", -> yes 227 | opentip.show() 228 | expect($(opentip.container).find(".content").html()).to.be """<div><span></span></div>""" 229 | 230 | it "should not escape the content if not @options.escapeContent", -> 231 | element = document.createElement "div" 232 | opentip = new Opentip element, "
", escapeContent: no 233 | sinon.stub opentip, "_triggerElementExists", -> yes 234 | opentip.show() 235 | expect($(opentip.container).find(".content > div > span").length).to.be 1 236 | 237 | it "should storeAndLock dimensions and reposition the element", -> 238 | element = document.createElement "div" 239 | opentip = new Opentip element, showOn: "click" 240 | sinon.stub opentip, "_storeAndLockDimensions" 241 | sinon.stub opentip, "reposition" 242 | opentip.visible = yes 243 | opentip._updateElementContent() 244 | expect(opentip._storeAndLockDimensions.callCount).to.equal 1 245 | expect(opentip.reposition.callCount).to.equal 1 246 | 247 | describe "_buildContainer()", -> 248 | element = document.createElement "div" 249 | opentip = null 250 | beforeEach -> 251 | opentip = new Opentip element, 252 | style: "glass" 253 | showEffect: "appear" 254 | hideEffect: "fade" 255 | 256 | it "should set the id", -> 257 | expect(adapter.attr opentip.container, "id").to.equal "opentip-" + opentip.id 258 | it "should set the classes", -> 259 | enderElement = $ adapter.unwrap opentip.container 260 | expect(enderElement.hasClass "opentip-container").to.be.ok() 261 | expect(enderElement.hasClass "hidden").to.be.ok() 262 | expect(enderElement.hasClass "style-glass").to.be.ok() 263 | expect(enderElement.hasClass "show-effect-appear").to.be.ok() 264 | expect(enderElement.hasClass "hide-effect-fade").to.be.ok() 265 | 266 | describe "_buildElements()", -> 267 | element = opentip = null 268 | 269 | beforeEach -> 270 | element = document.createElement "div" 271 | opentip = new Opentip element, "the content", "the title", hideTrigger: "closeButton", stem: "top left", ajax: "bla" 272 | opentip._buildElements() 273 | 274 | it "should add a h1 if title is provided", -> 275 | enderElement = $ adapter.unwrap opentip.container 276 | headerElement = enderElement.find "> .opentip > .header > h1" 277 | expect(headerElement.length).to.be.ok() 278 | expect(headerElement.html()).to.be "the title" 279 | 280 | it "should add a loading indicator if ajax", -> 281 | enderElement = $ adapter.unwrap opentip.container 282 | loadingElement = enderElement.find "> .opentip > .loading-indicator > span" 283 | expect(loadingElement.length).to.be.ok() 284 | expect(loadingElement.html()).to.be "↻" 285 | 286 | it "should add a close button if hideTrigger = close", -> 287 | enderElement = $ adapter.unwrap opentip.container 288 | closeButton = enderElement.find "> .opentip > .header > a.close > span" 289 | expect(closeButton.length).to.be.ok() 290 | expect(closeButton.html()).to.be "Close" 291 | 292 | describe "addAdapter()", -> 293 | it "should set the current adapter, and add the adapter to the list", -> 294 | expect(Opentip.adapters.testa).to.not.be.ok() 295 | testAdapter = { name: "testa" } 296 | Opentip.addAdapter testAdapter 297 | expect(Opentip.adapters.testa).to.equal testAdapter 298 | 299 | it "should use adapter.domReady to call findElements() with it" 300 | 301 | describe "_setupObservers()", -> 302 | it "should never setup the same observers twice" 303 | 304 | describe "_searchAndActivateCloseButtons()", -> 305 | it "should do what it says" 306 | 307 | describe "_activateFirstInput()", -> 308 | it "should do what it says", -> 309 | element = document.createElement "div" 310 | opentip = new Opentip element, "