mirror of
https://github.com/morrisjs/morris.js.git
synced 2024-11-13 07:11:12 +01:00
WIP: HTML hover refactor.
- Morris.Hover now encapsulates the hover object, with no graph-specific code. - Tests for Morris.Hover. - Add chai-jquery to test suite.
This commit is contained in:
parent
8080000a56
commit
6787fa7cff
8 changed files with 339 additions and 35 deletions
|
@ -1,19 +1,9 @@
|
||||||
div.morris-popup {
|
.morris-popup {
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
font: normal 13px/16px Arial, sans-serif;
|
color: #666;
|
||||||
color: #666;
|
background: rgba(255, 255, 255, 0.8);
|
||||||
background: rgba(255,255,255,.8);
|
border: solid 2px rgba(230, 230, 230, 0.8);
|
||||||
border: solid 2px rgba(230,230,230,.8);
|
}
|
||||||
|
|
||||||
h4, p {
|
|
||||||
font: normal 13px/16px Arial, sans-serif;
|
|
||||||
text-align: center;
|
|
||||||
color: #666;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 { font-weight: bold; }
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,22 +1,33 @@
|
||||||
class Morris.Hover
|
class Morris.Hover
|
||||||
# Displays contextual information in a floating HTML div.
|
# Displays contextual information in a floating HTML div.
|
||||||
#
|
|
||||||
|
@defaults:
|
||||||
|
class: 'morris-popup'
|
||||||
|
|
||||||
constructor: (options = {}) ->
|
constructor: (options = {}) ->
|
||||||
@options = $.extend {}, Morris.Hover.defaults, options
|
@options = $.extend {}, Morris.Hover.defaults, options
|
||||||
@el = $ "<div class='#{@options.class}'></div>"
|
@el = $ "<div class='#{@options.class}'></div>"
|
||||||
@el.hide()
|
@el.hide()
|
||||||
|
@options.parent.append(@el)
|
||||||
|
|
||||||
@defaults:
|
update: (x, y, data) ->
|
||||||
class: 'morris-popup'
|
@render(data)
|
||||||
allowOverflow: false
|
@show()
|
||||||
|
@moveTo(x, y)
|
||||||
|
|
||||||
show: (x, y, data) ->
|
render: (data) ->
|
||||||
if typeof @options.content is 'function'
|
if typeof @options.content is 'function'
|
||||||
@el.html @options.content(data)
|
@el.html @options.content(data)
|
||||||
else
|
else
|
||||||
@el.html @options.content
|
@el.html @options.content
|
||||||
|
|
||||||
|
moveTo: (x, y) ->
|
||||||
|
@el.css(
|
||||||
|
left: (x - @el.outerWidth() / 2) + "px"
|
||||||
|
top: (y - @el.outerHeight() - 10) + "px")
|
||||||
|
|
||||||
|
show: ->
|
||||||
@el.show()
|
@el.show()
|
||||||
|
|
||||||
hide: ->
|
hide: ->
|
||||||
@el.hide()
|
@el.hide()
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
div.morris-popup{border-radius:10px;position:absolute;z-index:1000;padding:6px;font:normal 13px/16px Arial,sans-serif;color:#666;background:rgba(255, 255, 255, 0.8);border:solid 2px rgba(230, 230, 230, 0.8);}div.morris-popup h4,div.morris-popup p{font:normal 13px/16px Arial,sans-serif;text-align:center;color:#666;margin:0;}
|
.morris-popup{border-radius:10px;position:absolute;z-index:1000;padding:6px;color:#666;background:rgba(255, 255, 255, 0.8);border:solid 2px rgba(230, 230, 230, 0.8);}
|
||||||
div.morris-popup h4{font-weight:bold;}
|
|
||||||
|
|
28
morris.js
28
morris.js
|
@ -603,6 +603,10 @@
|
||||||
|
|
||||||
Morris.Hover = (function() {
|
Morris.Hover = (function() {
|
||||||
|
|
||||||
|
Hover.defaults = {
|
||||||
|
"class": 'morris-popup'
|
||||||
|
};
|
||||||
|
|
||||||
function Hover(options) {
|
function Hover(options) {
|
||||||
if (options == null) {
|
if (options == null) {
|
||||||
options = {};
|
options = {};
|
||||||
|
@ -610,19 +614,31 @@
|
||||||
this.options = $.extend({}, Morris.Hover.defaults, options);
|
this.options = $.extend({}, Morris.Hover.defaults, options);
|
||||||
this.el = $("<div class='" + this.options["class"] + "'></div>");
|
this.el = $("<div class='" + this.options["class"] + "'></div>");
|
||||||
this.el.hide();
|
this.el.hide();
|
||||||
|
this.options.parent.append(this.el);
|
||||||
}
|
}
|
||||||
|
|
||||||
Hover.defaults = {
|
Hover.prototype.update = function(x, y, data) {
|
||||||
"class": 'morris-popup',
|
this.render(data);
|
||||||
allowOverflow: false
|
this.show();
|
||||||
|
return this.moveTo(x, y);
|
||||||
};
|
};
|
||||||
|
|
||||||
Hover.prototype.show = function(x, y, data) {
|
Hover.prototype.render = function(data) {
|
||||||
if (typeof this.options.content === 'function') {
|
if (typeof this.options.content === 'function') {
|
||||||
this.el.html(this.options.content(data));
|
return this.el.html(this.options.content(data));
|
||||||
} else {
|
} else {
|
||||||
this.el.html(this.options.content);
|
return this.el.html(this.options.content);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Hover.prototype.moveTo = function(x, y) {
|
||||||
|
return this.el.css({
|
||||||
|
left: (x - this.el.outerWidth() / 2) + "px",
|
||||||
|
top: (y - this.el.outerHeight() - 10) + "px"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Hover.prototype.show = function() {
|
||||||
return this.el.show();
|
return this.el.show();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
2
morris.min.js
vendored
2
morris.min.js
vendored
File diff suppressed because one or more lines are too long
54
spec/lib/hover_spec.coffee
Normal file
54
spec/lib/hover_spec.coffee
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
describe "Morris.Hover", ->
|
||||||
|
|
||||||
|
describe "with dummy content", ->
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
@hover = new Morris.Hover(
|
||||||
|
parent: $('#test'),
|
||||||
|
content: '<div style="width:84px;height:84px"></div>')
|
||||||
|
@element = $('#test .morris-popup')
|
||||||
|
|
||||||
|
it "should initialise a hidden, empty popup", ->
|
||||||
|
@element.should.exist
|
||||||
|
@element.should.be.hidden
|
||||||
|
@element.should.be.empty
|
||||||
|
|
||||||
|
describe "#show", ->
|
||||||
|
it "should show the popup", ->
|
||||||
|
@hover.show()
|
||||||
|
@element.should.be.visible
|
||||||
|
|
||||||
|
describe "#hide", ->
|
||||||
|
it "should hide the popup", ->
|
||||||
|
@hover.show()
|
||||||
|
@hover.hide()
|
||||||
|
@element.should.be.hidden
|
||||||
|
|
||||||
|
describe "#moveTo", ->
|
||||||
|
it "should hover the popup directly above the given point", ->
|
||||||
|
@hover.render()
|
||||||
|
@hover.moveTo(100, 150)
|
||||||
|
@element.should.have.css('left', '50px')
|
||||||
|
@element.should.have.css('top', '40px')
|
||||||
|
|
||||||
|
describe "#render", ->
|
||||||
|
it "should take content from a string", ->
|
||||||
|
hover = new Morris.Hover(parent: $('#test'), content: 'Hello, World!')
|
||||||
|
hover.render()
|
||||||
|
$('#test .morris-popup').html().should.equal 'Hello, World!'
|
||||||
|
|
||||||
|
it "should take content from a method", ->
|
||||||
|
hover = new Morris.Hover(parent: $('#test'), content: (x) -> "Hello, #{x}!")
|
||||||
|
hover.render('Tester')
|
||||||
|
$('#test .morris-popup').html().should.equal 'Hello, Tester!'
|
||||||
|
|
||||||
|
describe "#update", ->
|
||||||
|
it "should update content, show and reposition the popup", ->
|
||||||
|
hover = new Morris.Hover
|
||||||
|
parent: $('#test')
|
||||||
|
content: (x) -> "<div style='width:84px;height:84px'>Hello, #{x}!</div>"
|
||||||
|
hover.update(150, 200, 'Everyone')
|
||||||
|
el = $('#test .morris-popup')
|
||||||
|
el.should.have.css('left', '100px')
|
||||||
|
el.should.have.css('top', '90px')
|
||||||
|
el.should.have.text('Hello, Everyone!')
|
|
@ -3,6 +3,7 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>morris.js tests</title>
|
<title>morris.js tests</title>
|
||||||
<link rel="stylesheet" href="vendor/mocha-1.6.0.css" type="text/css" media="screen" />
|
<link rel="stylesheet" href="vendor/mocha-1.6.0.css" type="text/css" media="screen" />
|
||||||
|
<link rel="stylesheet" href="../morris.css" type="text/css" media="screen" />
|
||||||
<script src="vendor/jquery-1.8.2.min.js"></script>
|
<script src="vendor/jquery-1.8.2.min.js"></script>
|
||||||
<script type="text/javascript" src="vendor/raphael-2.1.0.min.js"></script>
|
<script type="text/javascript" src="vendor/raphael-2.1.0.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript" src="vendor/chai-1.3.0.js"></script>
|
<script type="text/javascript" src="vendor/chai-1.3.0.js"></script>
|
||||||
|
<script type="text/javascript" src="vendor/chai-jquery-1.1.0.js"></script>
|
||||||
<script type="text/javascript" src="vendor/sinon-1.5.0.js"></script>
|
<script type="text/javascript" src="vendor/sinon-1.5.0.js"></script>
|
||||||
<script type="text/javascript" src="vendor/sinon-chai-2.1.2.js"></script>
|
<script type="text/javascript" src="vendor/sinon-chai-2.1.2.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -23,7 +25,7 @@
|
||||||
|
|
||||||
<script type="text/javascript" src="../morris.js"></script>
|
<script type="text/javascript" src="../morris.js"></script>
|
||||||
<script type="text/javascript" src="../build/spec.js"></script>
|
<script type="text/javascript" src="../build/spec.js"></script>
|
||||||
<div id="test" style="visibility: hidden"></div>
|
<div id="test" style="width: 400px; height: 200px;"></div>
|
||||||
<script>
|
<script>
|
||||||
if (navigator.userAgent.indexOf('PhantomJS') < 0) {
|
if (navigator.userAgent.indexOf('PhantomJS') < 0) {
|
||||||
mocha.run();
|
mocha.run();
|
||||||
|
|
232
spec/vendor/chai-jquery-1.1.0.js
vendored
Normal file
232
spec/vendor/chai-jquery-1.1.0.js
vendored
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
(function (chaiJquery) {
|
||||||
|
// Module systems magic dance.
|
||||||
|
if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
|
||||||
|
// NodeJS
|
||||||
|
module.exports = chaiJquery;
|
||||||
|
} else if (typeof define === "function" && define.amd) {
|
||||||
|
// AMD
|
||||||
|
define(function () {
|
||||||
|
return chaiJquery;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Other environment (usually <script> tag): plug in to global chai instance directly.
|
||||||
|
chai.use(chaiJquery);
|
||||||
|
}
|
||||||
|
}(function (chai, utils) {
|
||||||
|
var inspect = utils.inspect,
|
||||||
|
flag = utils.flag;
|
||||||
|
|
||||||
|
jQuery.fn.inspect = function (depth) {
|
||||||
|
var el = jQuery('<div />').append(this.clone());
|
||||||
|
if (depth) {
|
||||||
|
var children = el.children();
|
||||||
|
while (depth-- > 0)
|
||||||
|
children = children.children();
|
||||||
|
children.html('...');
|
||||||
|
}
|
||||||
|
return el.html();
|
||||||
|
};
|
||||||
|
|
||||||
|
var props = {attr: 'attribute', css: 'CSS property'};
|
||||||
|
for (var prop in props) {
|
||||||
|
(function (prop, description) {
|
||||||
|
chai.Assertion.addMethod(prop, function (name, val) {
|
||||||
|
var actual = flag(this, 'object')[prop](name);
|
||||||
|
|
||||||
|
if (!flag(this, 'negate') || undefined === val) {
|
||||||
|
this.assert(
|
||||||
|
undefined !== actual
|
||||||
|
, 'expected #{this} to have a #{exp} ' + description
|
||||||
|
, 'expected #{this} not to have a #{exp} ' + description
|
||||||
|
, name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (undefined !== val) {
|
||||||
|
this.assert(
|
||||||
|
val === actual
|
||||||
|
, 'expected #{this} to have a ' + inspect(name) + ' ' + description + ' with the value #{exp}, but the value was #{act}'
|
||||||
|
, 'expected #{this} not to have a ' + inspect(name) + ' ' + description + ' with the value #{act}'
|
||||||
|
, val
|
||||||
|
, actual
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
flag(this, 'object', actual);
|
||||||
|
});
|
||||||
|
})(prop, props[prop]);
|
||||||
|
}
|
||||||
|
|
||||||
|
chai.Assertion.addMethod('data', function (name, val) {
|
||||||
|
// Work around a chai bug (https://github.com/logicalparadox/chai/issues/16)
|
||||||
|
if (flag(this, 'negate') && undefined !== val && undefined === flag(this, 'object').data(name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var assertion = new chai.Assertion(flag(this, 'object').data());
|
||||||
|
if (flag(this, 'negate'))
|
||||||
|
assertion = assertion.not;
|
||||||
|
return assertion.property(name, val);
|
||||||
|
});
|
||||||
|
|
||||||
|
chai.Assertion.addMethod('class', function (className) {
|
||||||
|
this.assert(
|
||||||
|
flag(this, 'object').hasClass(className)
|
||||||
|
, 'expected #{this} to have class #{exp}'
|
||||||
|
, 'expected #{this} not to have class #{exp}'
|
||||||
|
, className
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
chai.Assertion.addMethod('id', function (id) {
|
||||||
|
this.assert(
|
||||||
|
flag(this, 'object').attr('id') === id
|
||||||
|
, 'expected #{this} to have id #{exp}'
|
||||||
|
, 'expected #{this} not to have id #{exp}'
|
||||||
|
, id
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
chai.Assertion.addMethod('html', function (html) {
|
||||||
|
this.assert(
|
||||||
|
flag(this, 'object').html() === html
|
||||||
|
, 'expected #{this} to have HTML #{exp}'
|
||||||
|
, 'expected #{this} not to have HTML #{exp}'
|
||||||
|
, html
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
chai.Assertion.addMethod('text', function (text) {
|
||||||
|
this.assert(
|
||||||
|
flag(this, 'object').text() === text
|
||||||
|
, 'expected #{this} to have text #{exp}'
|
||||||
|
, 'expected #{this} not to have text #{exp}'
|
||||||
|
, text
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
chai.Assertion.addMethod('value', function (value) {
|
||||||
|
this.assert(
|
||||||
|
flag(this, 'object').val() === value
|
||||||
|
, 'expected #{this} to have value #{exp}'
|
||||||
|
, 'expected #{this} not to have value #{exp}'
|
||||||
|
, value
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
jQuery.each(['visible', 'hidden', 'selected', 'checked', 'disabled'], function (i, attr) {
|
||||||
|
chai.Assertion.addProperty(attr, function () {
|
||||||
|
this.assert(
|
||||||
|
flag(this, 'object').is(':' + attr)
|
||||||
|
, 'expected #{this} to be ' + attr
|
||||||
|
, 'expected #{this} not to be ' + attr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
chai.Assertion.overwriteProperty('exist', function (_super) {
|
||||||
|
return function () {
|
||||||
|
var obj = flag(this, 'object');
|
||||||
|
if (obj instanceof jQuery) {
|
||||||
|
this.assert(
|
||||||
|
obj.length > 0
|
||||||
|
, 'expected ' + inspect(obj.selector) + ' to exist'
|
||||||
|
, 'expected ' + inspect(obj.selector) + ' not to exist');
|
||||||
|
} else {
|
||||||
|
_super.apply(this, arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
chai.Assertion.overwriteProperty('empty', function (_super) {
|
||||||
|
return function () {
|
||||||
|
var obj = flag(this, 'object');
|
||||||
|
if (obj instanceof jQuery) {
|
||||||
|
this.assert(
|
||||||
|
obj.is(':empty')
|
||||||
|
, 'expected #{this} to be empty'
|
||||||
|
, 'expected #{this} not to be empty');
|
||||||
|
} else {
|
||||||
|
_super.apply(this, arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
chai.Assertion.overwriteProperty('be', function (_super) {
|
||||||
|
return function () {
|
||||||
|
var be = function (selector) {
|
||||||
|
var obj = flag(this, 'object');
|
||||||
|
if (obj instanceof jQuery) {
|
||||||
|
this.assert(
|
||||||
|
obj.is(selector)
|
||||||
|
, 'expected #{this} to be #{exp}'
|
||||||
|
, 'expected #{this} not to be #{exp}'
|
||||||
|
, selector
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_super.apply(this, arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
be.__proto__ = this;
|
||||||
|
return be;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
chai.Assertion.overwriteMethod('match', function (_super) {
|
||||||
|
return function (selector) {
|
||||||
|
var obj = flag(this, 'object');
|
||||||
|
if (obj instanceof jQuery) {
|
||||||
|
this.assert(
|
||||||
|
obj.is(selector)
|
||||||
|
, 'expected #{this} to match #{exp}'
|
||||||
|
, 'expected #{this} not to match #{exp}'
|
||||||
|
, selector
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_super.apply(this, arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
chai.Assertion.overwriteProperty('contain', function (_super) {
|
||||||
|
return function () {
|
||||||
|
_super.call(this);
|
||||||
|
var contain = function (text) {
|
||||||
|
var obj = flag(this, 'object');
|
||||||
|
if (obj instanceof jQuery) {
|
||||||
|
this.assert(
|
||||||
|
obj.is(':contains(\'' + text + '\')')
|
||||||
|
, 'expected #{this} to contain #{exp}'
|
||||||
|
, 'expected #{this} not to contain #{exp}'
|
||||||
|
, text
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Function.prototype.apply.call(_super.call(this), this, arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
contain.__proto__ = this;
|
||||||
|
return contain;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
chai.Assertion.overwriteProperty('have', function (_super) {
|
||||||
|
return function () {
|
||||||
|
var obj = flag(this, 'object');
|
||||||
|
if (obj instanceof jQuery) {
|
||||||
|
var have = function (selector) {
|
||||||
|
this.assert(
|
||||||
|
// Using find() rather than has() to work around a jQuery bug:
|
||||||
|
// http://bugs.jquery.com/ticket/11706
|
||||||
|
obj.find(selector).length > 0
|
||||||
|
, 'expected #{this} to have #{exp}'
|
||||||
|
, 'expected #{this} not to have #{exp}'
|
||||||
|
, selector
|
||||||
|
);
|
||||||
|
};
|
||||||
|
have.__proto__ = this;
|
||||||
|
return have;
|
||||||
|
} else {
|
||||||
|
_super.call(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}));
|
Loading…
Reference in a new issue