Browse Source

Button widget annotations: improve unit tests, simplify code and remove labels

Modern browsers support styling radio buttons and checkboxes with CSS.
This makes the implementation much easier, and the fallback for older
browsers is still decent.
Tim van der Meij 8 years ago
parent
commit
a428899b3c
  1. 17
      src/core/annotation.js
  2. 29
      src/display/annotation_layer.js
  3. 28
      test/annotation_layer_test.css
  4. 100
      test/unit/annotation_layer_spec.js
  5. 74
      web/annotation_layer_builder.css

17
src/core/annotation.js

@ -773,17 +773,18 @@ var ButtonWidgetAnnotation = (function ButtonWidgetAnnotationClosure() {
function ButtonWidgetAnnotation(params) { function ButtonWidgetAnnotation(params) {
WidgetAnnotation.call(this, params); WidgetAnnotation.call(this, params);
this.data.pushbutton = this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON); this.data.checkBox = !this.hasFieldFlag(AnnotationFieldFlag.RADIO) &&
this.data.radio = !this.data.pushbutton && !this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
this.hasFieldFlag(AnnotationFieldFlag.RADIO); if (this.data.checkBox) {
if (!isName(this.data.fieldValue)) {
if (isName(this.data.fieldValue)) { return;
}
this.data.fieldValue = this.data.fieldValue.name; this.data.fieldValue = this.data.fieldValue.name;
} else {
warn('Button widget annotation: field value is not a `Name` object.');
} }
if (this.data.radio) { this.data.radioButton = this.hasFieldFlag(AnnotationFieldFlag.RADIO) &&
!this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
if (this.data.radioButton) {
this.data.fieldValue = this.data.buttonValue = null; this.data.fieldValue = this.data.buttonValue = null;
// The parent field's `V` entry holds a `Name` object with the appearance // The parent field's `V` entry holds a `Name` object with the appearance

29
src/display/annotation_layer.js

@ -77,14 +77,12 @@ AnnotationElementFactory.prototype =
case 'Tx': case 'Tx':
return new TextWidgetAnnotationElement(parameters); return new TextWidgetAnnotationElement(parameters);
case 'Btn': case 'Btn':
if (!parameters.data.pushbutton) { if (parameters.data.radioButton) {
if (parameters.data.radio) {
return new RadioButtonWidgetAnnotationElement(parameters); return new RadioButtonWidgetAnnotationElement(parameters);
} else { } else if (parameters.data.checkBox) {
return new CheckboxWidgetAnnotationElement(parameters); return new CheckboxWidgetAnnotationElement(parameters);
}
} else { } else {
warn('Unimplemented push button'); warn('Unimplemented button widget annotation: pushbutton');
} }
break; break;
case 'Ch': case 'Ch':
@ -152,7 +150,6 @@ var AnnotationElement = (function AnnotationElementClosure() {
var height = data.rect[3] - data.rect[1]; var height = data.rect[3] - data.rect[1];
container.setAttribute('data-annotation-id', data.id); container.setAttribute('data-annotation-id', data.id);
container.setAttribute('data-annotation-name', data.fieldName);
// Do *not* modify `data.rect`, since that will corrupt the annotation // Do *not* modify `data.rect`, since that will corrupt the annotation
// position on subsequent calls to `_createContainer` (see issue 6804). // position on subsequent calls to `_createContainer` (see issue 6804).
@ -568,15 +565,11 @@ var CheckboxWidgetAnnotationElement =
var element = document.createElement('input'); var element = document.createElement('input');
element.disabled = this.data.readOnly; element.disabled = this.data.readOnly;
element.type = 'checkbox'; element.type = 'checkbox';
element.id = this.data.fieldName;
if (this.data.fieldValue && this.data.fieldValue !== 'Off') { if (this.data.fieldValue && this.data.fieldValue !== 'Off') {
element.checked = true; element.setAttribute('checked', true);
} }
this.container.appendChild(element);
element = document.createElement('label');
element.htmlFor = this.data.fieldName;
this.container.appendChild(element);
this.container.appendChild(element);
return this.container; return this.container;
} }
}); });
@ -608,22 +601,16 @@ var RadioButtonWidgetAnnotationElement =
this.container.className = 'buttonWidgetAnnotation radioButton'; this.container.className = 'buttonWidgetAnnotation radioButton';
var element = document.createElement('input'); var element = document.createElement('input');
var id = this.data.fieldName + '.' + this.data.buttonValue;
element.disabled = this.data.readOnly; element.disabled = this.data.readOnly;
element.type = 'radio'; element.type = 'radio';
element.id = id;
element.name = this.data.fieldName; element.name = this.data.fieldName;
element.value = this.data.buttonValue;
if (this.data.fieldValue === this.data.buttonValue) { if (this.data.fieldValue === this.data.buttonValue) {
element.checked = true; element.setAttribute('checked', true);
} }
this.container.appendChild(element);
element = document.createElement('label');
element.htmlFor = id;
this.container.appendChild(element);
this.container.appendChild(element);
return this.container; return this.container;
}, }
}); });
return RadioButtonWidgetAnnotationElement; return RadioButtonWidgetAnnotationElement;

28
test/annotation_layer_test.css

@ -15,6 +15,11 @@
/* Used for annotation layer tests */ /* Used for annotation layer tests */
* {
padding: 0;
margin: 0;
}
.annotationLayer { .annotationLayer {
position: absolute; position: absolute;
left: 0; left: 0;
@ -46,8 +51,8 @@
.annotationLayer .textWidgetAnnotation input, .annotationLayer .textWidgetAnnotation input,
.annotationLayer .textWidgetAnnotation textarea, .annotationLayer .textWidgetAnnotation textarea,
.annotationLayer .choiceWidgetAnnotation select, .annotationLayer .choiceWidgetAnnotation select,
.annotationLayer .buttonWidgetAnnotation.checkBox label, .annotationLayer .buttonWidgetAnnotation.checkBox input,
.annotationLayer .buttonWidgetAnnotation.radioButton label { .annotationLayer .buttonWidgetAnnotation.radioButton input {
background-color: rgba(0, 54, 255, 0.13); background-color: rgba(0, 54, 255, 0.13);
border: 1px solid transparent; border: 1px solid transparent;
box-sizing: border-box; box-sizing: border-box;
@ -67,8 +72,8 @@
.annotationLayer .textWidgetAnnotation input[disabled], .annotationLayer .textWidgetAnnotation input[disabled],
.annotationLayer .textWidgetAnnotation textarea[disabled], .annotationLayer .textWidgetAnnotation textarea[disabled],
.annotationLayer .choiceWidgetAnnotation select[disabled], .annotationLayer .choiceWidgetAnnotation select[disabled],
.annotationLayer .buttonWidgetAnnotation.checkBox input[disabled] + label, .annotationLayer .buttonWidgetAnnotation.checkBox input[disabled],
.annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] + label { .annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] {
background: none; background: none;
border: 1px solid transparent; border: 1px solid transparent;
} }
@ -79,19 +84,12 @@
padding-right: 0; padding-right: 0;
} }
.annotationLayer .buttonWidgetAnnotation.checkBox label,
.annotationLayer .buttonWidgetAnnotation.radioButton label {
position: absolute;
}
.annotationLayer .buttonWidgetAnnotation.checkBox input, .annotationLayer .buttonWidgetAnnotation.checkBox input,
.annotationLayer .buttonWidgetAnnotation.radioButton input { .annotationLayer .buttonWidgetAnnotation.radioButton input {
position: absolute; -webkit-appearance: none;
left: -9999px; -moz-appearance: none;
} -ms-appearance: none;
appearance: none;
.annotationLayer .buttonWidgetAnnotation.radioButton label {
border-radius: 50%;
} }
.annotationLayer .popupAnnotation { .annotationLayer .popupAnnotation {

100
test/unit/annotation_layer_spec.js

@ -869,94 +869,62 @@ describe('Annotation layer', function() {
}); });
}); });
describe('CheckboxWidgetAnnotation', function() { describe('ButtonWidgetAnnotation', function() {
var checkboxWidgetDict; var buttonWidgetDict;
beforeEach(function (done) { beforeEach(function (done) {
checkboxWidgetDict = new Dict(); buttonWidgetDict = new Dict();
checkboxWidgetDict.set('Type', Name.get('Annot')); buttonWidgetDict.set('Type', Name.get('Annot'));
checkboxWidgetDict.set('Subtype', Name.get('Widget')); buttonWidgetDict.set('Subtype', Name.get('Widget'));
checkboxWidgetDict.set('FT', Name.get('Btn')); buttonWidgetDict.set('FT', Name.get('Btn'));
done(); done();
}); });
afterEach(function () { afterEach(function () {
checkboxWidgetDict = null; buttonWidgetDict = null;
}); });
it('should have proper flags', it('should handle checkboxes', function() {
function() { buttonWidgetDict.set('V', Name.get('1'));
var checkboxWidgetRef = new Ref(124, 0);
var xref = new XRefMock([
{ ref: checkboxWidgetRef, data: checkboxWidgetDict, }
]);
var checkboxWidgetAnnotation = var buttonWidgetRef = new Ref(124, 0);
annotationFactory.create(xref, checkboxWidgetRef);
expect(checkboxWidgetAnnotation.data.radio).toEqual(false);
expect(checkboxWidgetAnnotation.data.pushbutton).toEqual(false);
expect(checkboxWidgetAnnotation.data.fieldValue).toEqual(null);
});
it('should have a proper value',
function() {
checkboxWidgetDict.set('V', Name.get('1'));
var checkboxWidgetRef = new Ref(124, 0);
var xref = new XRefMock([ var xref = new XRefMock([
{ ref: checkboxWidgetRef, data: checkboxWidgetDict, } { ref: buttonWidgetRef, data: buttonWidgetDict, }
]); ]);
var checkboxWidgetAnnotation = var buttonWidgetAnnotation =
annotationFactory.create(xref, checkboxWidgetRef); annotationFactory.create(xref, buttonWidgetRef);
expect(checkboxWidgetAnnotation.data.fieldValue).toEqual('1'); expect(buttonWidgetAnnotation.data.checkBox).toEqual(true);
}); expect(buttonWidgetAnnotation.data.fieldValue).toEqual('1');
}); expect(buttonWidgetAnnotation.data.radioButton).toEqual(false);
describe('RadioButtonWidgetAnnotation', function() {
var radioButtonWidgetDict;
beforeEach(function (done) {
radioButtonWidgetDict = new Dict();
radioButtonWidgetDict.set('Type', Name.get('Annot'));
radioButtonWidgetDict.set('Subtype', Name.get('Widget'));
radioButtonWidgetDict.set('FT', Name.get('Btn'));
radioButtonWidgetDict.set('Ff', AnnotationFieldFlag.RADIO);
done();
}); });
afterEach(function () { it('should handle radio buttons', function() {
radioButtonWidgetDict = null; var parentDict = new Dict();
}); parentDict.set('V', Name.get('1'));
it('should have proper flags', var normalAppearanceStateDict = new Dict();
function() { normalAppearanceStateDict.set('2', null);
var radioButtonWidgetRef = new Ref(124, 0);
var xref = new XRefMock([
{ ref: radioButtonWidgetRef, data: radioButtonWidgetDict, }
]);
var radioButtonWidgetAnnotation = var appearanceStatesDict = new Dict();
annotationFactory.create(xref, radioButtonWidgetRef); appearanceStatesDict.set('N', normalAppearanceStateDict);
expect(radioButtonWidgetAnnotation.data.radio).toEqual(true);
expect(radioButtonWidgetAnnotation.data.pushbutton).toEqual(false);
expect(radioButtonWidgetAnnotation.data.fieldValue).toEqual(null);
});
it('should have a proper value', buttonWidgetDict.set('Ff', AnnotationFieldFlag.RADIO);
function() { buttonWidgetDict.set('Parent', parentDict);
radioButtonWidgetDict.set('V', Name.get('1')); buttonWidgetDict.set('AP', appearanceStatesDict);
var radioButtonWidgetRef = new Ref(124, 0); var buttonWidgetRef = new Ref(124, 0);
var xref = new XRefMock([ var xref = new XRefMock([
{ ref: radioButtonWidgetRef, data: radioButtonWidgetDict, } { ref: buttonWidgetRef, data: buttonWidgetDict, }
]); ]);
var radioButtonWidgetAnnotation = var buttonWidgetAnnotation =
annotationFactory.create(xref, radioButtonWidgetRef); annotationFactory.create(xref, buttonWidgetRef);
expect(radioButtonWidgetAnnotation.data.fieldValue).toEqual('1'); expect(buttonWidgetAnnotation.data.checkBox).toEqual(false);
expect(buttonWidgetAnnotation.data.radioButton).toEqual(true);
expect(buttonWidgetAnnotation.data.fieldValue).toEqual('1');
expect(buttonWidgetAnnotation.data.buttonValue).toEqual('2');
}); });
}); });

74
web/annotation_layer_builder.css

@ -43,7 +43,9 @@
.annotationLayer .textWidgetAnnotation input, .annotationLayer .textWidgetAnnotation input,
.annotationLayer .textWidgetAnnotation textarea, .annotationLayer .textWidgetAnnotation textarea,
.annotationLayer .choiceWidgetAnnotation select { .annotationLayer .choiceWidgetAnnotation select,
.annotationLayer .buttonWidgetAnnotation.checkBox input,
.annotationLayer .buttonWidgetAnnotation.radioButton input {
background-color: rgba(0, 54, 255, 0.13); background-color: rgba(0, 54, 255, 0.13);
border: 1px solid transparent; border: 1px solid transparent;
box-sizing: border-box; box-sizing: border-box;
@ -63,8 +65,8 @@
.annotationLayer .textWidgetAnnotation input[disabled], .annotationLayer .textWidgetAnnotation input[disabled],
.annotationLayer .textWidgetAnnotation textarea[disabled], .annotationLayer .textWidgetAnnotation textarea[disabled],
.annotationLayer .choiceWidgetAnnotation select[disabled], .annotationLayer .choiceWidgetAnnotation select[disabled],
.annotationLayer .buttonWidgetAnnotation.checkBox input[disabled] + label, .annotationLayer .buttonWidgetAnnotation.checkBox input[disabled],
.annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] + label { .annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] {
background: none; background: none;
border: 1px solid transparent; border: 1px solid transparent;
cursor: not-allowed; cursor: not-allowed;
@ -72,7 +74,9 @@
.annotationLayer .textWidgetAnnotation input:hover, .annotationLayer .textWidgetAnnotation input:hover,
.annotationLayer .textWidgetAnnotation textarea:hover, .annotationLayer .textWidgetAnnotation textarea:hover,
.annotationLayer .choiceWidgetAnnotation select:hover { .annotationLayer .choiceWidgetAnnotation select:hover,
.annotationLayer .buttonWidgetAnnotation.checkBox input:hover,
.annotationLayer .buttonWidgetAnnotation.radioButton input:hover {
border: 1px solid #000; border: 1px solid #000;
} }
@ -99,66 +103,12 @@
width: 115%; width: 115%;
} }
.annotationLayer .buttonWidgetAnnotation.checkBox label,
.annotationLayer .buttonWidgetAnnotation.radioButton label {
background-color: rgba(0, 54, 255, 0.13);
border: 1px solid transparent;
box-sizing: border-box;
cursor: pointer;
height: 100%;
position: absolute;
width: 100%;
}
.annotationLayer .buttonWidgetAnnotation.checkBox input, .annotationLayer .buttonWidgetAnnotation.checkBox input,
.annotationLayer .buttonWidgetAnnotation.radioButton input { .annotationLayer .buttonWidgetAnnotation.radioButton input {
position: absolute; -webkit-appearance: none;
left: -9999px; -moz-appearance: none;
} -ms-appearance: none;
appearance: none;
.annotationLayer .buttonWidgetAnnotation.radioButton label {
border-radius: 50%;
}
.annotationLayer .buttonWidgetAnnotation.checkBox label:hover,
.annotationLayer .buttonWidgetAnnotation.checkBox label:focus,
.annotationLayer .buttonWidgetAnnotation.checkBox input:focus + label,
.annotationLayer .buttonWidgetAnnotation.radioButton label:hover,
.annotationLayer .buttonWidgetAnnotation.radioButton label:focus,
.annotationLayer .buttonWidgetAnnotation.radioButton input:focus + label {
border: 1px solid #000;
}
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked + label:before,
.annotationLayer .buttonWidgetAnnotation.radioButton input:checked + label:before {
content: '';
left: 50%;
top: 50%;
position: absolute;
}
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked + label:before {
border-bottom: 1px solid #000;
border-left: 1px solid #000;
height: 25%;
-webkit-transform: translate(-50%, -65%) rotateZ(-45deg);
-moz-transform: translate(-50%, -65%) rotateZ(-45deg);
-o-transform: translate(-50%, -65%) rotateZ(-45deg);
-ms-transform: translate(-50%, -65%) rotateZ(-45deg);
transform: translate(-50%, -65%) rotateZ(-45deg);
width: 60%;
}
.annotationLayer .buttonWidgetAnnotation.radioButton input:checked + label:before {
background-color: #000;
border-radius: 50%;
height: 50%;
-webkit-transform: translate(-50%, -50%);
-moz-transform: translate(-50%, -50%);
-o-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
width: 50%;
} }
.annotationLayer .popupWrapper { .annotationLayer .popupWrapper {

Loading…
Cancel
Save