We are moving to Git Issues for bug tracking in future releases. During transition, content will be in both tools. If you'd like to file a new bug, please create an issue.

View | Details | Raw Unified | Return to bug 7950
Collapse All | Expand All

(-)a/js/controllers/SiteHandler.js (-47 / +81 lines)
 Lines 40-45   jQuery.pkp.controllers = jQuery.pkp.controllers || { }; Link Here 
40
		this.bind('updateHeader', this.updateHeaderHandler_);
40
		this.bind('updateHeader', this.updateHeaderHandler_);
41
		this.bind('updateSidebar', this.updateSidebarHandler_);
41
		this.bind('updateSidebar', this.updateSidebarHandler_);
42
		this.bind('callWhenClickOutside', this.callWhenClickOutsideHandler_);
42
		this.bind('callWhenClickOutside', this.callWhenClickOutsideHandler_);
43
		this.bind('mousedown', this.mouseDownHandler_);
43
44
44
		// Listen for grid initialized events so the inline help
45
		// Listen for grid initialized events so the inline help
45
		// can be shown or hidden.
46
		// can be shown or hidden.
 Lines 74-79   jQuery.pkp.controllers = jQuery.pkp.controllers || { }; Link Here 
74
				this.unregisterUnsavedFormElement_));
75
				this.unregisterUnsavedFormElement_));
75
		this.bind('unregisterAllForms', this.callbackWrapper(
76
		this.bind('unregisterAllForms', this.callbackWrapper(
76
				this.unregisterAllFormElements_));
77
				this.unregisterAllFormElements_));
78
79
		this.outsideClickChecks_ = {};
77
	};
80
	};
78
	$.pkp.classes.Helper.inherits(
81
	$.pkp.classes.Helper.inherits(
79
			$.pkp.controllers.SiteHandler, $.pkp.classes.Handler);
82
			$.pkp.controllers.SiteHandler, $.pkp.classes.Handler);
 Lines 91-96   jQuery.pkp.controllers = jQuery.pkp.controllers || { }; Link Here 
91
94
92
95
93
	/**
96
	/**
97
	 * Object with data to be used when checking if user
98
	 * clicked outside a site element. See callWhenClickOutsideHandler_()
99
	 * to check the expected check options.
100
	 * @private
101
	 * @type {Object}
102
	 */
103
	$.pkp.controllers.SiteHandler.prototype.outsideClickChecks_ = null;
104
105
106
	/**
94
	 * A state variable to store the form elements that have unsaved data.
107
	 * A state variable to store the form elements that have unsaved data.
95
	 * @private
108
	 * @private
96
	 * @type {Array}
109
	 * @type {Array}
 Lines 99-105   jQuery.pkp.controllers = jQuery.pkp.controllers || { }; Link Here 
99
112
100
113
101
	//
114
	//
102
	// Public static method.
115
	// Public static methods.
103
	//
116
	//
104
	/**
117
	/**
105
	 * Callback used by the tinyMCE plugin to trigger the tinyMCEInitialized
118
	 * Callback used by the tinyMCE plugin to trigger the tinyMCEInitialized
 Lines 304-311   jQuery.pkp.controllers = jQuery.pkp.controllers || { }; Link Here 
304
317
305
318
306
	/**
319
	/**
307
	 * Binds a click event to this element so we can track if user
320
	 * Call when click outside event handler. Stores the event
308
	 * clicked outside the passed element or not.
321
	 * parameters as checks to be used later by mouse down handler so we
322
	 * can track if user clicked outside the passed element or not.
309
	 * @param {HTMLElement} sourceElement The element that issued the
323
	 * @param {HTMLElement} sourceElement The element that issued the
310
	 *  callWhenClickOutside event.
324
	 *  callWhenClickOutside event.
311
	 * @param {Event} event The "call when click outside" event.
325
	 * @param {Event} event The "call when click outside" event.
 Lines 320-394   jQuery.pkp.controllers = jQuery.pkp.controllers || { }; Link Here 
320
	 */
334
	 */
321
	$.pkp.controllers.SiteHandler.prototype.callWhenClickOutsideHandler_ =
335
	$.pkp.controllers.SiteHandler.prototype.callWhenClickOutsideHandler_ =
322
			function(sourceElement, event, eventParams) {
336
			function(sourceElement, event, eventParams) {
323
		if (this.callWhenClickOutsideEventParams_ !== undefined) {
337
		// Check the required parameters.
324
			throw Error('Another widget is already using this structure.');
338
		if (eventParams.container == undefined) {
339
			return;
340
		}
341
342
		if (eventParams.callback == undefined) {
343
			return;
325
		}
344
		}
326
345
327
		this.callWhenClickOutsideEventParams_ = eventParams;
346
		var id = eventParams.container.attr('id');
328
		setTimeout(this.callbackWrapper(function() {
347
		this.outsideClickChecks_[id] = eventParams;
329
			this.bind('mousedown', this.checkOutsideClickHandler_);
330
		}), 25);
331
	};
348
	};
332
349
333
350
334
	/**
351
	/**
335
	 * Mouse down event handler, used by the callWhenClickOutside event handler
352
	 * Mouse down event handler attached to the site element.
336
	 * to test if user clicked outside an element or not. If true, will
337
	 * callback a function. Can optionally avoid the callback
338
	 * when a modal widget is loaded.
339
	 * @param {HTMLElement} sourceElement The element that issued the
353
	 * @param {HTMLElement} sourceElement The element that issued the
340
	 *  click event.
354
	 *  click event.
341
	 * @param {Event} event The "mousedown" event.
355
	 * @param {Event} event The "mousedown" event.
342
	 * @return {boolean} Event handling status.
356
	 * @return {boolean} Event handling status.
343
	 * @private
357
	 * @private
344
	 */
358
	 */
345
	$.pkp.controllers.SiteHandler.prototype.checkOutsideClickHandler_ =
359
	$.pkp.controllers.SiteHandler.prototype.mouseDownHandler_ =
346
			function(sourceElement, event) {
360
			function(sourceElement, event) {
347
361
348
		var $container, callback;
362
		var $container, callback;
349
		if (this.callWhenClickOutsideEventParams_ !== undefined) {
363
		if (!$.isEmptyObject(this.outsideClickChecks_)) {
350
			// Start checking the paramenters.
364
			for (var id in this.outsideClickChecks_) {
351
			if (this.callWhenClickOutsideEventParams_.container !== undefined) {
365
				this.processOutsideClickCheck_(
352
				// Store the container element.
366
						this.outsideClickChecks_[id], event);
353
				$container = this.callWhenClickOutsideEventParams_.container;
354
			} else {
355
				// Need a container, return.
356
				return false;
357
			}
367
			}
368
		}
358
369
359
			if (this.callWhenClickOutsideEventParams_.callback !== undefined) {
370
		return true;
360
				// Store the callback.
371
	};
361
				callback = this.callWhenClickOutsideEventParams_.callback;
362
			} else {
363
				// Need the callback, return.
364
				return false;
365
			}
366
372
367
			if (this.callWhenClickOutsideEventParams_.skipWhenVisibleModals !==
368
					undefined) {
369
				if (this.callWhenClickOutsideEventParams_.skipWhenVisibleModals) {
370
					if (this.getHtmlElement().find('div.ui-dialog').length > 0) {
371
						// Found a modal, return.
372
						return false;
373
					}
374
				}
375
			}
376
373
377
			// Do the click origin checking.
374
	/**
378
			if ($container.has(event.target).length === 0) {
375
	 * Check if the passed event target is outside the element
379
				// Unbind this click handler.
376
	 * inside the passed check data. If true and no other check
380
				this.unbind('mousedown', this.checkOutsideClickHandler_);
377
	 * option avoids it, use the callback.
378
	 * @param {Object} checkOptions Object with data to be used to
379
	 * check the click.
380
	 * @param {Event} event The click event to be checked.
381
	 * @returns {Boolean} Whether the check was processed or not.
382
	 */
383
	$.pkp.controllers.SiteHandler.prototype.processOutsideClickCheck_ =
384
			function(checkOptions, event) {
385
386
		// Make sure we have a click event.
387
		if (event.type !== 'click' &&
388
				event.type !== 'mousedown' && event.type !== 'mouseup') {
389
			throw Error('Can not check outside click with the passed event: ' +
390
					event.type + '.');
391
			return false;
392
		}
393
394
		// Get the container element.
395
		var $container = checkOptions.container;
381
396
382
				// Clean the original event parameters data.
397
		// Doesn't make sense to check an outside click
383
				this.callWhenClickOutsideEventParams_ = undefined;
398
		// with an invisible element, so skip test if
399
		// container is hidden.
400
		if ($container.is(':hidden')) {
401
			return false;
402
		}
384
403
385
				if (!$container.is(':hidden')) {
404
		// Check for the visible modals option.
386
					// Only considered outside if the container is visible.
405
		if (checkOptions.skipWhenVisibleModals !==
387
					callback();
406
				undefined) {
407
			if (checkOptions.skipWhenVisibleModals) {
408
				if (this.getHtmlElement().find('div.ui-dialog').length > 0) {
409
					// Found a modal, return.
410
					return false;
388
				}
411
				}
389
			}
412
			}
390
		}
413
		}
391
414
415
		// Do the click origin checking.
416
		if ($container.has(event.target).length === 0) {
417
418
			// Once the check was processed, delete it.
419
			delete this.outsideClickChecks_[$container.attr('id')];
420
421
			checkOptions.callback();
422
423
			return true;
424
		}
425
392
		return false;
426
		return false;
393
	};
427
	};
394
428
(-)a/js/controllers/form/MultilingualInputHandler.js (-64 / +112 lines)
 Lines 36-49    Link Here 
36
		}
36
		}
37
37
38
		$popoverNode
38
		$popoverNode
39
				.focus(this.callbackWrapper(this.multilingualShow));
39
				.focus(this.callbackWrapper(this.focusHandler_));
40
		// Bind to the blur of any of the inputs to to check if we should close.
40
		// Bind to the blur of any of the inputs to to check if we should close.
41
		$popover.find(':input').
41
		$popover.find(':input').
42
				blur(this.callbackWrapper(this.multilingualHide));
42
				blur(this.callbackWrapper(this.blurHandler_));
43
43
44
		this.publishEvent('tinyMCEInitialized');
44
		this.publishEvent('tinyMCEInitialized');
45
45
46
		this.bind('tinyMCEInitialized', this.handleTinyMCEEvents_);
46
		this.bind('tinyMCEInitialized', this.tinyMCEInitHandler_);
47
	};
47
	};
48
	$.pkp.classes.Helper.inherits(
48
	$.pkp.classes.Helper.inherits(
49
			$.pkp.controllers.form.MultilingualInputHandler,
49
			$.pkp.controllers.form.MultilingualInputHandler,
 Lines 51-82    Link Here 
51
51
52
52
53
	//
53
	//
54
	// Private properties
54
	// Private helper methods.
55
	//
55
	//
56
	/**
56
	/**
57
	 * This timer is used to control closing the
57
	 * Focus event handler. This is attached to all primary inputs.
58
	 * popover when blur events are detected.
58
	 *
59
	 * @private
59
	 * @param {HTMLElement} multilingualInput The primary multilingual
60
	 * @type {Object}
60
	 * element.
61
	 * @param {Event} event The focus event.
61
	 */
62
	 */
62
	$.pkp.controllers.form.MultilingualInputHandler.prototype.
63
	$.pkp.controllers.form.MultilingualInputHandler.prototype.focusHandler_ =
63
			popoverCloseTimer_ = null;
64
			function(multilingualInput, event) {
65
66
		this.showPopover_();
67
	};
64
68
65
69
66
	//
67
	// Public methods
68
	//
69
	/**
70
	/**
70
	 * Internal callback called to show additional languages for a
71
	 * Blur event handler. This is attached to all inputs inside this
71
	 * multilingual input
72
	 * popover element.
72
	 *
73
	 *
73
	 * @param {HTMLElement} multilingualInput The primary multilingual
74
	 * @param {HTMLElement} multilingualInput The element in the
74
	 *		element in the set to show.
75
	 * multilingual set to hide.
75
	 * @param {Event} event The event that triggered the action.
76
	 * @param {Event} event The event that triggered the action.
77
	 * @return {Boolean} Return true to continue the event handling.
76
	 */
78
	 */
77
	$.pkp.controllers.form.MultilingualInputHandler.prototype.multilingualShow =
79
	$.pkp.controllers.form.MultilingualInputHandler.prototype.blurHandler_ =
78
			function(multilingualInput, event) {
80
			function(multilingualInput, event) {
79
81
82
		// Use a timeout to give the other element a chance to acquire the focus.
83
		setTimeout(this.callbackWrapper(function() {
84
			if (!this.hasElementInFocus_()) {
85
				this.hidePopover_();
86
			}
87
		}), 0);
88
89
		return true;
90
	};
91
92
93
	/**
94
	 * Hide this popover.
95
	 * @private
96
	 */
97
	$.pkp.controllers.form.MultilingualInputHandler.prototype.hidePopover_ =
98
			function() {
99
		var $popover = this.getHtmlElement();
100
		$popover.removeClass('localization_popover_container_focus');
101
		$popover.find('.localization_popover').hide();
102
	};
103
104
105
	/**
106
	 * Show this popover.
107
	 * @private
108
	 */
109
	$.pkp.controllers.form.MultilingualInputHandler.prototype.showPopover_ =
110
			function() {
80
		var $popover = this.getHtmlElement();
111
		var $popover = this.getHtmlElement();
81
		$popover.addClass('localization_popover_container_focus');
112
		$popover.addClass('localization_popover_container_focus');
82
113
 Lines 89-141    Link Here 
89
120
90
121
91
	/**
122
	/**
92
	 * Internal callback called to hide additional languages for a
123
	 * Test if any of the elements inside this popover has focus.
93
	 * multilingual input
124
	 * @return {boolean}
94
	 *
95
	 * @param {HTMLElement} multilingualInput The element in the
96
	 *		multilingual set to hide.
97
	 * @param {Event} event The event that triggered the action.
98
	 */
125
	 */
99
	$.pkp.controllers.form.MultilingualInputHandler.prototype.multilingualHide =
126
	$.pkp.controllers.form.MultilingualInputHandler.prototype.hasElementInFocus_ =
100
			function(multilingualInput, event) {
127
			function() {
101
128
102
		// Use a timeout to give the other element a chance to acquire the focus.
129
		var $popover = this.getHtmlElement();
103
		setTimeout(this.callbackWrapper(function() {
130
104
			var $popover = this.getHtmlElement();
131
		// Do the test.
105
			var found = false;
132
		if ($popover.has(document.activeElement).length) {
106
			// Test if any of the other elements has the focus.
133
			return true;
107
			$popover.find(':input').each(function(index, elem) {
134
		} else {
108
				if (elem === document.activeElement) {
135
			return false;
109
					found = true;
136
		}
110
				}
111
			});
112
			// If none of them have the focus, we can hide the pop over.
113
			if (!found) {
114
				$popover.removeClass('localization_popover_container_focus');
115
				$popover.find('.localization_popover').hide();
116
			}
117
		}), 0);
118
	};
137
	};
119
138
120
139
121
	/**
140
	/**
122
	 * tinyMCE initialized event handler, it will attach focus and blur
141
	 * TinyMCE initialized event handler, it will attach focus and blur
123
	 * event handlers to the tinyMCE window element.
142
	 * event handlers to the tinyMCE window element, and it will also
143
	 * fix some small issues related to the way tinyMCE editor behaves
144
	 * across different browsers.
124
	 * @param {HTMLElement} input The input element that triggered the
145
	 * @param {HTMLElement} input The input element that triggered the
125
	 * event.
146
	 * event.
126
	 * @param {Event} event The tinyMCE initialized event.
147
	 * @param {Event} event The tinyMCE initialized event.
127
	 * @param {Object} tinyMCEObject The tinyMCE object inside this
148
	 * @param {Object} tinyMCEObject The tinyMCE object inside this
128
	 * multilingual element handler that was initialized.
149
	 * multilingual element handler that was initialized.
129
	 */
150
	 */
130
	$.pkp.controllers.form.MultilingualInputHandler.prototype.handleTinyMCEEvents_ =
151
	$.pkp.controllers.form.MultilingualInputHandler.prototype.tinyMCEInitHandler_ =
131
			function(input, event, tinyMCEObject) {
152
			function(input, event, tinyMCEObject) {
132
		var editorId = tinyMCEObject.editorId;
153
		var editorId = tinyMCEObject.editorId;
154
155
		// This hack is needed so the focus event is triggered correctly in IE8.
156
		// We just adjust the body element height inside the tinyMCE editor
157
		// instance to a percent of the original text area height, so when users
158
		// click inside an empty tinyMCE editor the target will be the body element
159
		// and the focus event will be triggered.
160
		var textAreaHeight = $('#' + tinyMCEObject.editorId).height();
161
		$(tinyMCEObject.getBody()).height((textAreaHeight / 100) * 78);
162
133
		$(tinyMCEObject.getWin()).focus(
163
		$(tinyMCEObject.getWin()).focus(
134
				this.callbackWrapper(function() {
164
				this.callbackWrapper(function() {
135
165
166
			// We need also to close the multilingual popover when user clicks
167
			// outside the popover element. The blur event is not enough because
168
			// sometimes (with text selected in editor) Chrome will consider the
169
			// tinyMCE editor as still active and that will avoid the popover to
170
			// close (see the first check of the blur handler, just above).
171
			//
172
			// Firefox will also not completely focus on tinyMCE editors after
173
			// comming back from fullscreen mode (the callback to focus the
174
			// editor when set content will only trigger the focus handler that
175
			// we attach here, but will not move the cursor inside the tinyMCE
176
			// editor). Then, if user clicks outside the popover, it will not
177
			// close because no blur event will be triggered.
178
			this.trigger('callWhenClickOutside', {
179
				container: this.getHtmlElement(),
180
				callback: this.callbackWrapper(this.hidePopover_),
181
				skipWhenVisibleModals: false
182
			});
183
136
			// Create a callback for the set content event, so we can
184
			// Create a callback for the set content event, so we can
137
			// still show the multilingual input if user is back from an
185
			// still show the multilingual input if user is back from
138
			// image insertion, html edit or fullscreen mode.
186
			// fullscreen mode.
139
			var setContentCallback = this.callbackWrapper(
187
			var setContentCallback = this.callbackWrapper(
140
					function(tinyMCEObject) {
188
					function(tinyMCEObject) {
141
				var $tinyWindow = $(tinyMCEObject.getWin());
189
				var $tinyWindow = $(tinyMCEObject.getWin());
 Lines 151-175    Link Here 
151
			// Add the set content callback.
199
			// Add the set content callback.
152
			tinyMCEObject.onSetContent.add(setContentCallback);
200
			tinyMCEObject.onSetContent.add(setContentCallback);
153
201
154
			clearTimeout(this.popoverTimer);
202
			this.showPopover_();
155
			var $popoverContainer = this.getHtmlElement();
156
			$popoverContainer.
157
				addClass('localization_popover_container_focus');
158
			var $localizationPopover = $popoverContainer.find('.localization_popover');
159
160
			$localizationPopover.find('iframe').width($popoverContainer.width() -1);
161
			$localizationPopover.show();
162
	    }));
203
	    }));
204
163
		$(tinyMCEObject.getWin()).blur(
205
		$(tinyMCEObject.getWin()).blur(
164
				this.callbackWrapper(function() {
206
				this.callbackWrapper(function() {
165
			// set a short timer to prevent the next popover from closing.
207
166
			// this allows time for the next click event from the
208
			// Check if the active document element is still the tinyMCE
167
			// TinyMCE editor to cancel the timer.
209
			// editor. If true, return false. This will avoid closing the
168
			this.popoverTimer = setTimeout(this.callbackWrapper(
210
			// popover if user is just inserting an image or editing the
169
					function() {
211
			// html source, for example (both actions open a new window).
170
				this.getHtmlElement().
212
			if ($(tinyMCEObject.getContainer()).find('iframe').attr('id') ==
171
					removeClass('localization_popover_container_focus');
213
				$(document.activeElement).attr('id')) {
172
				$('.localization_popover', this.getHtmlElement()).hide();
214
				return false;
215
			}
216
217
			// Use a timeout to give the other element a chance to acquire the focus.
218
			setTimeout(this.callbackWrapper(function() {
219
				if (!this.hasElementInFocus_()) {
220
					this.hidePopover_();
221
				}
173
			}), 0);
222
			}), 0);
174
	    }));
223
	    }));
175
	};
224
	};
176
- 

Return to bug 7950