mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-10-31 11:20:09 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			387 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			387 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| /**********************************************************************
 | |
| * 
 | |
| *
 | |
| *
 | |
| **********************************************************************/
 | |
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
 | |
| (function(require){ var module={} // make module AMD/node compatible...
 | |
| /*********************************************************************/
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*********************************************************************/
 | |
| 
 | |
| var selectElemText =
 | |
| module.selectElemText = 
 | |
| function(elem){
 | |
| 	var range = document.createRange()
 | |
| 	range.selectNodeContents(elem)
 | |
| 	var sel = window.getSelection()
 | |
| 	sel.removeAllRanges()
 | |
| 	sel.addRange(range)
 | |
| }
 | |
| 
 | |
| 
 | |
| // XXX make this global...
 | |
| var getCaretOffset = 
 | |
| module.getCaretOffset =
 | |
| function(elem){
 | |
| 	var s = window.getSelection()
 | |
| 	if(s.rangeCount == 0){
 | |
| 		return -1
 | |
| 	}
 | |
| 	var r = s.getRangeAt(0)
 | |
| 	var pre = r.cloneRange()
 | |
| 	pre.selectNodeContents(elem)
 | |
| 	pre.setEnd(r.endContainer, r.endOffset)
 | |
| 	return pre.toString().length || 0
 | |
| }
 | |
| 
 | |
| 
 | |
| var selectionCollapsed =
 | |
| module.selectionCollapsed =
 | |
| function(elem){
 | |
| 		var s = window.getSelection()
 | |
| 		if(s.rangeCount == 0){
 | |
| 			return false
 | |
| 		}
 | |
| 		return s.getRangeAt(0).cloneRange().collapsed
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| //---------------------------------------------------------------------
 | |
| // XXX experiment
 | |
| if(typeof(jQuery) != typeof(undefined)){
 | |
| 	jQuery.fn._drag = function(){
 | |
| 		var dragging = false
 | |
| 		var s, 
 | |
| 			px, py
 | |
| 
 | |
| 		var elem = $(this)
 | |
| 			.on('mousedown touchstart', function(evt){
 | |
| 				dragging = true
 | |
| 				px = evt.clientX
 | |
| 				px = evt.clientY
 | |
| 
 | |
| 				s = elem.rscale()
 | |
| 			})
 | |
| 			.on('mousemove touchmove', function(evt){
 | |
| 				if(!dragging){
 | |
| 					return
 | |
| 				}
 | |
| 
 | |
| 				var x = evt.clientX 
 | |
| 				var dx = px - x
 | |
| 				px = x
 | |
| 
 | |
| 				var y = evt.clientY 
 | |
| 				var dy = py - y
 | |
| 				py = y
 | |
| 
 | |
| 				elem
 | |
| 					.velocity('stop')
 | |
| 					.velocity({
 | |
| 						translateX: '-=' + (dx / s),
 | |
| 						translateY: '-=' + (dy / s),
 | |
| 					}, 0)
 | |
| 			})
 | |
| 			.on('mouseup touchend', function(evt){
 | |
| 				dragging = false
 | |
| 				elem.velocity('stop')
 | |
| 			})
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 	jQuery.fn.selectText = function(mode){
 | |
| 		var range = document.createRange()
 | |
| 
 | |
| 		this.each(function(){
 | |
| 			range.selectNodeContents(this)
 | |
| 		})
 | |
| 
 | |
| 		var sel = window.getSelection()
 | |
| 		sel.removeAllRanges()
 | |
| 
 | |
| 		mode === null
 | |
| 			|| sel.addRange(range)
 | |
| 
 | |
| 		return this
 | |
| 	}
 | |
| 	jQuery.fn.deselectText = function(){ this.selectText(null) }
 | |
| 	jQuery.fn.caretOffset = function(){ return getCaretOffset(this) }
 | |
| 	jQuery.fn.selectionCollapsed = function(){ return selectionCollapsed(this) }
 | |
| 
 | |
| 
 | |
| 	var keyboard = require('lib/keyboard')
 | |
| 
 | |
| 	// Make element editable...
 | |
| 	//
 | |
| 	// Options format:
 | |
| 	// 	{
 | |
| 	// 		// activate (focus) element...
 | |
| 	// 		//
 | |
| 	// 		// NOTE: this will also select the element text...
 | |
| 	// 		activate: false,
 | |
| 	//
 | |
| 	// 		// set multi-line edit mode...
 | |
| 	// 		multiline: false,
 | |
| 	//
 | |
| 	// 		// if true in multi-line mode, accept filed on Enter, while
 | |
| 	// 		// ctrl-Enter / meta-Enter insert a new line; otherwise
 | |
| 	// 		// ctrl-Enter / meta-Enter will accept the edit.
 | |
| 	// 		accept_on_enter: true,
 | |
| 	//
 | |
| 	// 		// clear element value on edit...
 | |
| 	// 		clear_on_edit: false,
 | |
| 	//
 | |
| 	// 		// reset value on commit/abort...
 | |
| 	// 		// XXX revise default...
 | |
| 	// 		reset_on_commit: true,
 | |
| 	// 		reset_on_abort: true,
 | |
| 	//
 | |
| 	// 		// blur element on commit/abort...
 | |
| 	// 		blur_on_commit: false,
 | |
| 	// 		blur_on_abort: false,
 | |
| 	//
 | |
| 	// 		// restore focus before disabling the editor...
 | |
| 	// 		keep_focus_on_parent: true,
 | |
| 	//
 | |
| 	// 		// clear selection on commit/abort...
 | |
| 	// 		clear_selection_on_commit: true,
 | |
| 	// 		clear_selection_on_abort: true,
 | |
| 	//
 | |
| 	// 		// If false unhandled key events will not be propagated to 
 | |
| 	// 		// parents...
 | |
| 	// 		propagate_unhandled_keys: true,
 | |
| 	//
 | |
| 	// 		// If false the element editable state will not be reset to
 | |
| 	// 		// the original when edit is done...
 | |
| 	// 		reset_on_done: true,
 | |
| 	//
 | |
| 	// 		// Keys that will abort the edit...
 | |
| 	// 		abort_keys: [
 | |
| 	// 			'Esc',
 | |
| 	// 		],
 | |
| 	// 	}
 | |
| 	//
 | |
| 	// This listens to these events triggerable by user:
 | |
| 	// 	'edit-commit'		- will commit changes, this is passed the 
 | |
| 	// 							new text just edited.
 | |
| 	// 	'edit-abort'		- will reset field, this is passed the 
 | |
| 	// 							original text before the edit.
 | |
| 	//
 | |
| 	// These events get passed the relevant text, but the element is 
 | |
| 	// likely to be already reset to a different state, to get the 
 | |
| 	// element before any state change is started use one of the 
 | |
| 	// following variants:
 | |
| 	// 	'edit-committing'	- triggered within 'edit-commit' but before
 | |
| 	// 							anything is changed, gets passed the final
 | |
| 	// 							text (same as 'edit-commit')
 | |
| 	// 	'edit-aborting'		- triggered within 'edit-abort' but before 
 | |
| 	// 							anything is changed, gets passed the 
 | |
| 	// 							original text value (same as 'edit-abort')
 | |
| 	//
 | |
| 	// This will try and preserve element content DOM when resetting.
 | |
| 	//
 | |
| 	//
 | |
| 	// NOTE: removing tabindex will reset focus, so this will attempt to 
 | |
| 	// 		focus the first [tabindex] element up the tree...
 | |
| 	//
 | |
| 	// XXX add option to select the element on start or just focus it...
 | |
| 	// 		.activate: 'select' | true | false
 | |
| 	// XXX should we just use form elements???
 | |
| 	// 		...it's a trade-off, here we add editing functionality and fight
 | |
| 	// 		a bit the original function, in an input we'll need to fight part
 | |
| 	// 		of the editing functionality and add our own navigation...
 | |
| 	// XXX move this to a more generic spot...
 | |
| 	jQuery.fn.makeEditable = function(options){
 | |
| 		var that = this
 | |
| 
 | |
| 		if(options == false){
 | |
| 			this
 | |
| 				.removeAttr('contenteditable')
 | |
| 				.removeAttr('tabindex')
 | |
| 				.removeClass('editable-field')
 | |
| 
 | |
| 			var events = this.data('editable-field-events')
 | |
| 			for(var e in events){
 | |
| 				this.off(e, events[e])
 | |
| 			}
 | |
| 			this.removeData('editable-field-events')
 | |
| 
 | |
| 			return this
 | |
| 		}
 | |
| 
 | |
| 		options = Object.assign({
 | |
| 			// defaults...
 | |
| 			activate: false,
 | |
| 			multiline: false,
 | |
| 			accept_on_enter: true,
 | |
| 			clear_on_edit: false,
 | |
| 			reset_on_commit: true,
 | |
| 			reset_on_abort: true,
 | |
| 			blur_on_commit: false,
 | |
| 			blur_on_abort: false,
 | |
| 			keep_focus_on_parent: true,
 | |
| 			clear_selection_on_commit: true,
 | |
| 			clear_selection_on_abort: true,
 | |
| 			propagate_unhandled_keys: true,
 | |
| 			reset_on_done: true,
 | |
| 			abort_keys: ['Esc'],
 | |
| 		}, options || {})
 | |
| 
 | |
| 		var original_text = this[0].innerText
 | |
| 		var original_dom = document.createDocumentFragment()
 | |
| 		this[0].childNodes
 | |
| 			.forEach(function(node){ 
 | |
| 				original_dom.appendChild(node.cloneNode(true)) })
 | |
| 		var resetOriginal = function(){
 | |
| 			//that.text(original_text)
 | |
| 			that[0].innerHTML = ''
 | |
| 			that[0].appendChild(original_dom.cloneNode(true))
 | |
| 		}
 | |
| 
 | |
| 		this.prop('contenteditable', true)
 | |
| 
 | |
| 		options.activate 
 | |
| 			&& options.clear_on_edit 
 | |
| 			// XXX this for some reason breaks on click...
 | |
| 			&& this.text('')
 | |
| 
 | |
| 		// NOTE: this will also focus the element...
 | |
| 		options.activate 
 | |
| 			&& this.selectText()
 | |
| 
 | |
| 		// do not setup handlers more than once...
 | |
| 		if(!this.hasClass('editable-field')){
 | |
| 			var events = {}
 | |
| 			this
 | |
| 				// make the element focusable and selectable...
 | |
| 				.attr('tabindex', '0')
 | |
| 				.addClass('editable-field')
 | |
| 				.keydown(events.keydown = function(in_evt){ 
 | |
| 					var evt = window.event || in_evt
 | |
| 					if(!that.prop('contenteditable')){
 | |
| 						return
 | |
| 					}
 | |
| 
 | |
| 					evt.stopPropagation() 
 | |
| 
 | |
| 					var c = getCaretOffset(this)
 | |
| 					var collapsed = selectionCollapsed(this) 
 | |
| 					var n = keyboard.code2key(evt.keyCode)
 | |
| 
 | |
| 					// abort...
 | |
| 					if((options.abort_keys || []).indexOf(n) >= 0){
 | |
| 						that.trigger('edit-abort', original_text)
 | |
| 
 | |
| 					// done -- single line...
 | |
| 					} else if(n == 'Enter' 
 | |
| 							&& !options.multiline){
 | |
| 						evt.preventDefault()
 | |
| 						that.trigger('edit-commit', that[0].innerText)
 | |
| 
 | |
| 					// done -- multi-line...
 | |
| 					} else if(options.multiline 
 | |
| 							&& n == 'Enter' 
 | |
| 							&& (options.accept_on_enter ?
 | |
| 								!(evt.ctrlKey || evt.shiftKey || evt.metaKey) 
 | |
| 								: (evt.ctrlKey || evt.shiftKey || evt.metaKey)) ){
 | |
| 						evt.preventDefault()
 | |
| 						that.trigger('edit-commit', that[0].innerText)
 | |
| 
 | |
| 					// multi-line keep keys...
 | |
| 					} else if(options.multiline 
 | |
| 							&& options.accept_on_enter ? 
 | |
| 								(n == 'Enter' 
 | |
| 								 	&& (evt.ctrlKey || evt.shiftKey || evt.metaKey))
 | |
| 								: n == 'Enter'){
 | |
| 						return
 | |
| 
 | |
| 					// multi-line arrow keys -- keep key iff not at first/last position...
 | |
| 					} else if(options.multiline
 | |
| 							&& n == 'Up'
 | |
| 							&& (c > 0 || !collapsed)){
 | |
| 						return
 | |
| 					} else if(options.multiline
 | |
| 							&& n == 'Down'
 | |
| 							&& (c < $(this).text().length || !collapsed)){
 | |
| 						return
 | |
| 					} else if(n == 'Up' || n == 'Down'){
 | |
| 						evt.preventDefault()
 | |
| 						that.trigger('edit-commit', that[0].innerText)
 | |
| 
 | |
| 					// continue handling...
 | |
| 					} else if(options.propagate_unhandled_keys){
 | |
| 						// NOTE: jQuery can't reuse browser events, this 
 | |
| 						// 		we need to pass a jq event/proxy here...
 | |
| 						$(this).parent().trigger(in_evt || evt)
 | |
| 					}
 | |
| 				})
 | |
| 				.blur(events.blur = function(){
 | |
| 					window.getSelection().removeAllRanges()
 | |
| 				})
 | |
| 				.on('focus click', events['focus click'] = function(evt){
 | |
| 					evt.stopPropagation()
 | |
| 					options.clear_on_edit 
 | |
| 						&& $(this)
 | |
| 							.text('')
 | |
| 							.selectText()
 | |
| 				})
 | |
| 				// user triggerable events...
 | |
| 				.on('edit-abort', events['edit-abort'] = function(evt, text){
 | |
| 					that.trigger('edit-aborting', text)
 | |
| 
 | |
| 					options.clear_selection_on_abort
 | |
| 						&& window.getSelection().removeAllRanges()
 | |
| 
 | |
| 					// reset original value...
 | |
| 					options.reset_on_abort
 | |
| 						&& resetOriginal() 
 | |
| 					options.blur_on_abort
 | |
| 						&& this.blur() 
 | |
| 
 | |
| 					// restore focus on parent...
 | |
| 					options.keep_focus_on_parent
 | |
| 						&& that.parents('[tabindex]').first().focus()
 | |
| 
 | |
| 					options.reset_on_done
 | |
| 						&& that.makeEditable(false)
 | |
| 				})
 | |
| 				.on('edit-commit', events['edit-commit'] = function(evt, text){
 | |
| 					that.trigger('edit-committing', text)
 | |
| 
 | |
| 					options.clear_selection_on_commit
 | |
| 						&& window.getSelection().removeAllRanges()
 | |
| 
 | |
| 					// reset original value...
 | |
| 					options.reset_on_commit
 | |
| 						&& resetOriginal() 
 | |
| 					options.blur_on_commit
 | |
| 						&& this.blur() 
 | |
| 
 | |
| 					// restore focus on parent...
 | |
| 					options.keep_focus_on_parent
 | |
| 						&& that.parents('[tabindex]').first().focus()
 | |
| 
 | |
| 					options.reset_on_done
 | |
| 						&& that.makeEditable(false)
 | |
| 				})
 | |
| 
 | |
| 			this.data('editable-field-events', events)
 | |
| 		}
 | |
| 
 | |
| 		return this
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /**********************************************************************
 | |
| * vim:set ts=4 sw=4 :                               */ return module })
 |