Skip to content

Instantly share code, notes, and snippets.

@adjohu
Created December 6, 2011 16:10
Show Gist options
  • Save adjohu/1438739 to your computer and use it in GitHub Desktop.
Save adjohu/1438739 to your computer and use it in GitHub Desktop.
# Defaults
grid = [10,10]
container = "#container"
# Button types
window.buttons =
text :
html : '<div>
<div class="content">
Double click to edit me
</div>
</div>'
dblclick : ->
# Capture arguments in case this is being called by something else
button_type = arguments[0]
ckeditor_change_callback = arguments[1] or -> true
content_el = $(this).children('.content')
content = content_el.html()
$this = $(this)
# Make new instance of CKEditor -> remove current if exists.
editor = CKEDITOR.instances.editor
if editor?
CKEDITOR.remove editor
editor.destroy()
ckeditor_config = {}
if button_type? and button_type is 'Source'
ckeditor_config.toolbar = 'Source'
ckeditor_config.startupMode = 'source'
$('#editor').ckeditor(ckeditor_config)
editor = CKEDITOR.instances.editor
# Add .edits content to ckeditor, add event handler to update .edit
editor.setData(content)
editor.on 'change', ->
if ckeditor_change_callback editor
content_el.html editor.getData()
resizeBox($this)
layer.update $this.data('layer')
getValue : ->
$(this).children('.content').html()
layerTitle : ->
if($(@).text(buttons.text.getValue()).substring(0,20) != '')
$(@).text(buttons.text.getValue()).substring(0,20) + '...'
else
$(@).text(buttons.text.getValue())
image :
html : '<div>
<a href="#null"/>
<strong>Doubleclick to upload image</strong>
</div>'
dblclick : ->
$.modal({
ajax: '/editor/ajax/images/id/'+id,
title: 'Image Library',
overlayClose: true,
beforeLoad: ->
$.upload()
})
$(@).children('strong').remove()
getValue : ->
$this = $(this)
value = $this.children('a')
randomcontainer = $('<div />')
value.clone().appendTo(randomcontainer)
value = randomcontainer.html()
layerTitle : ->
'Image'
box :
html: '<div style="border:3px solid #ccc;">
</div>'
dblclick : ->
getValue : ->
layerTitle : ->
'Box'
media :
html : '<div>
<div class="content">
Double click to edit me
</div>
<div class="content_cover">
</div>
</div>'
dblclick : ->
# Do the same a text button (CKEDITOR) but make it a source only ckeditor
self = @
args = ['Source', (editor) ->
editor = CKEDITOR.instances.editor
$self = $ self
content_el = $self.find '.content'
# Work with editor content
new_content = editor.getData()
# Try to force youtube to html5 mode
new_content = new_content.replace /(<iframe.*? src=")(.*?youtube.*?)?(".*?>)/gi, '$1$2?html5=1$3'
# Append html
content_el.html new_content
# .edit should resize to fit media
height = 0
width = 0
media = content_el.children(':visible')
media.each ->
$this = $(this)
this_height = $(this).outerHeight true
this_width = $(this).outerWidth true
height = this_height if this_height > height
width = this_width if this_width > width
$self.css(width:width,height:height)
false
]
buttons['text'].dblclick.apply @, args
getValue : ->
$(this).children('.content')?.html()
layerTitle : ->
'media'
form : # We extend buttons.media for other properties. Anything defined here takes precedence.
inherits : 'media'
layerTitle : ->
'form'
background :
html: '<div></div>'
dblclick: ->
getValue : ->
layerTitle : ->
'Background'
# Sort out button inheritance. Really should have used classes for this but fuck it :D
$.each(buttons, (idx, val)->
if this.hasOwnProperty 'inherits'
inherits = this.inherits
buttons[idx] = $.extend true, {}, buttons[inherits], this
)
# Edit object containing functions that will run on .edits
edit =
select : (el, calledByLayer = false) ->
#select the matching layer
layer.select( $(el).data('layer'), true ) if not calledByLayer
# This should be the only edit with class .current and only draggable .edit
$('.edit.current').removeClass('current').draggable('disable').resizable('disable')
type = $(el).data('type')
$(el).addClass('current')
if type isnt 'background' # Background shouldn't be dragged/resized
$(el).draggable('enable').resizable('enable')
delete : (el) ->
el = $(el)
if el.length > 0
return if el.data('type') is 'background'
#$.get('/ajax/globe/delete/tbl/e/id/' + el.data('id'))
$('#layers').data('deleted').push(el.data('id'))
el.data('layer').remove()
el.remove()
# Layer object containing functions to run on layers
layer =
select : (el, calledByEdit = false) ->
editEl = $(el).data 'el'
type = $(el).data 'type'
# Select matching .edit
edit.select(editEl, true) if not calledByEdit
# make this the selected layer
$('#layers .current').removeClass 'current'
$(el).addClass 'current'
# Display relevant properties panel
# also, populate it with the current properties
$('#properties input').each(->
# Current value
val = @.value
# Special cases dependant on name
switch this.name
when "link"
val = $(editEl).children('a').attr 'href'
else #Css property
val = $(editEl).css $(this).attr('name')
if this.name.indexOf('color') > -1
if val.length > 7
val = rgb2hex(val)
$(this).val val
$(this).trigger('change')
)
$('#properties li').hide()
$('.properties_' + type).show()
# Updates the layer title
update : (el) ->
$this = $(el)
title = buttons[$this.data 'type'].layerTitle.apply $this.data 'el'
$this.children('a.title').html(title)
setImageAttr = (el) ->
$(el).attr('alt')
# Arrange layers by z-index (highest at top)
orderLayers = ->
layers = $('#layers')
$('li:not(:last)', layers).sortElements (a,b) ->
$(b).data('z-index') - $(a).data('z-index')
resizeBox = (el) ->
el.stop().animate(
height: el.children('.content').height() + "px"
)
###
Adds a layer to the layers panel and links the layer to the .edit and vice versa
@param htmlElement el - the .edit to link to the layer
###
addLayer = (el) ->
layers = $('#layers')
# Sort out z-index of edit
zIndex = parseInt( $(el).css('z-index') ,10)
zIndex = 0 if isNaN zIndex
# Add new layer
new_layer = $('<li />')
.data(
'el' : el
'type' : $(el).data 'type'
'z-index' : zIndex
)
.click ->
layer.select(this)
# This array contains the innerHTML of the layer
layerContents = [
$('<a class="title" href="#">Layer</a>'),
$('<a class="delete_edit" href="#">x</a>').click ->
edit.delete el
]
$.each layerContents, ->
new_layer.append this
# Brand New layer (rather than loaded layer) should be highest
if zIndex is 0
highest_zindex = layers.children().eq(0).css('z-index')
new_layer.data 'z-index', highest_zindex + 1
new_layer.css 'z-index', new_layer.data 'z-index'
layers.prepend new_layer
# Link this layer to the .edit
$(el).data(
'layer' : new_layer
)
layer.update new_layer
orderLayers()
window.observeEdits = ->
$('.edit:not(.ui-draggable)')
.append('<div class="gridline gridline_x"></div><div class="gridline gridline_y"></div>')
.mouseup ->
edit.select @
.dblclick ->
# get edit type (eg. text / img etc) and match against buttons to find dblclick function
buttons[$(@).data 'type'].dblclick.apply @
.draggable(
disabled : true
grid : grid
#containment : container
# functions to run during drag events
start : (evt) ->
# show gridlines
$(this).children('.gridline').show()
drag : (evt, ui) ->
# Force draggable to stay on grid
ui.position.left -= ui.position.left % grid[0]
ui.position.top -= ui.position.top % grid[1]
# Make sure draggable doesn't completely leave container
[left,top,the_edit] = [ui.position.left, ui.position.top, ui.helper]
min_left = 0 - the_edit.width()
min_top = 0 - the_edit.height()
max_left = $(container).width()
max_top = $(container).height()
ui.position.left = min_left + 10 if left <= min_left
ui.position.top = min_top + 10 if top <= min_top
ui.position.left = max_left - 10 if left >= max_left
ui.position.top = max_top - 10 if top >= max_top
stop : (evt) ->
# hide gridlines
$(this).children('.gridline').hide()
)
.resizable(
disabled : true
grid : grid
handles : 'n, e, s, w, ne, se, sw, nw '
resize : (evt,ui) ->
# Force resize to stay on grid
if $(ui.element).hasClass('edit_text')
ui.size.width -= ui.size.width % grid[0]
else
$(this).find('img').css('height', ui.size.height + 'px').css('width', ui.size.width + 'px')
stop: (event, ui) ->
if $(ui.element).hasClass 'edit_text'
resizeBox $(this)
)
.each ->
console.log this
type = @className.match(/edit_([^\s]*)/)[1]
$(@).data('type', type)
if type == 'image'
$(this).resizable( "option", "grid", [0,0] )
addLayer @
# Make sure there are no unused layers kicking around.
$('#layers li').each(->
# Is the linked edit in the DOM
el = $(this).data 'el'
html = document.body.parentNode
while (el)
if el is html
return true
el = el.parentNode
$(this).remove()
)
class Clipboard
contents = null
copy : (el, oncomplete) ->
if not oncomplete then oncomplete = ->
$el = $(el)
new_el = $el.clone()
new_el.removeClass('ui-draggable')
contents = new_el
oncomplete()
cut : (el) ->
@copy el, ->
edit.delete(el)
paste : ->
$(container).append contents.clone()
observeEdits()
window.clipboard = new Clipboard
class Undo
undoStack = []
pushState : ->
state = $('#content').children().eq(0).clone(true,true)
undoStack.push state
popState : ->
if undoStack.length > 0
$('#content').children().eq(0).remove()
$('#content').prepend(undoStack.pop())
observeEdits()
window.undo = new Undo
$ ->
# Events for copy and paste.
document.oncopy = ->
clipboard.copy $('.edit.current')
document.oncut = ->
clipboard.cut $('.edit.current')
document.onpaste = ->
clipboard.paste()
# Right click menu - we are using a modified version of context menu seen here:
# http://stackoverflow.com/questions/6778258/jquery-contextmenu-live
# it works using jquery .live so will apply to all elements that match selector,
# now and in the future.
menu = $('#rightclickmenu')
# Disable some options for background
###$('.edit').live 'mousedown', -> menu.enableContextMenuItems()
$('.edit.edit_background').live 'mousedown', ->
menu.disableContextMenuItems()
allowed_for_background = "#paste"
menu.enableContextMenuItems allowed_for_background
###
# Set up menu
###
$('.edit').contextMenu { menu : 'rightclickmenu' }, (action,el,pos) ->
$el = $(el)
switch action
when "edit"
buttons[$el.data 'type'].dblclick.apply el
when "copy"
clipboard.copy $el
when "cut"
clipboard.cut $el
when "paste"
clipboard.paste()
when "delete"
edit.delete $el
###
# Set up context menu
when_background = (command, opt) ->
$trigger = opt.$trigger
console.log($trigger.hasClass('edit_background'))
$trigger.hasClass 'edit_background' and command isnt 'paste'
$.contextMenu
selector : '.edit'
items :
cut :
name : 'cut'
disabled : when_background
copy :
name : 'copy'
disabled: when_background
# Initialize stuff
$('#layers').data 'deleted',[]
if vid is 0
variation(0,true)
if $('.edit_background').length is 0
do createBackground = ->
edit_background = $('<div class="edit edit_background"/>').css(
left: 0
top: 0
width: '100%'
height: '100%'
).appendTo('#container')
# Click on background layer
$('.edit_background').trigger 'click'
observeEdits()
# key shortcuts
$(window).keyup (event) ->
if event.keyCode is 46 # Delete key - delete current .edit
edit.delete $('.edit.current')
# Button actions
$('.save').click ->
$.loading(true,'Saving.. items')
savePage(false)
false
$('.newVar').click ->
$.loading(true,'Saving..')
variation(0,true)
false
$('.finishBox a').click ->
$.loading(true,'Saving..')
savePage(true)
false
# Changes a property on the current .edit
window.changeProperty = (prop, val) ->
# Not entiiirreeely sure why this isn't .edit.current
el = $('#layers .current').data('el')
# Special cases
switch prop
when "link"
$(el).children('a').attr 'href', val
else
$(el).css prop,val
# Observe properties inputs to change css props on current selected .edit
$('#properties input')
.change ->
# Get property and value
prop = @.name
val = @.value
changeProperty prop, val
.each ->
self = @
# make it a colorpicker
if this.name.indexOf('color') > -1
val = @.value
$(this).ColorPicker(
onBeforeShow : ->
$(@).ColorPickerSetColor(val)
onChange : (hsb,hex,rgb) ->
$(self).val("#" + hex).trigger 'change'
# Special case for background, invert outline of edits
if self.id is 'main-background-color'
$('.edit').css(
'outline-color', invert(rgb)
)
)
$(@).val rgb2hex(val)
if $(this).hasClass 'slider'
$(this).slider()
# Toolbox items can be dragged onto the container to create new
# text fields/ images / etc...
$('#toolbox a')
.draggable(
helper : 'clone'
)
# We will clone the 'helper' when we finish dragging
.bind 'dragstop', (event,ui) ->
# Find the position relative to the container
{left,top} = ui.position
left -= $(container).position().left
top -= $(container).position().top
# Find the button in the button object that matches this button
button_class = @className.match(/button_([^\s]*)/)[1]
button = buttons[button_class]
$(button.html)
.addClass('edit')
.addClass("edit_#{button_class}")
.appendTo(container)
.css
left : left
top : top
# Call observeEdits to make this act as one of the .edits
observeEdits()
# Make layers draggable and auto become draggable.
$('#layers').sortable(
axis : 'y'
# Background shouldn't be sortable
items : '> li:not(:last)'
update : (evt, ui) ->
# Order the edits by z-index consecutively.
layers = $('#layers > li')
layers.reverse()
layers.each (idx) ->
zidx = idx + 1
$(this).data 'z-index', zidx
$($(this).data 'el').css 'z-index', zidx
layer.update @
)
# Stop .edit inner <a>s from leaving the page.
$('.edit a').live('click', -> false)
changePreviewLink = (v) ->
$('.previewBox a').attr('href', path + '/' + id + '&vid=' + v + '&mode=preview')
# Create a new variation
variation = (v, hide) ->
editData = {
cid: id
vid: v
# Position data
width : $(container).width()
height : $(container).height()
bgcolor: $(container).css('background-color')
}
$.post(
'/editor/ajax/variation/'
editData
(data) ->
if hide
$.loading(false,'')
window.vid = data
if v is 0
$('.variationBox ul').find('.current').removeClass('current')
$('.variationBox ul').append('<li><p class="current"><a href="/editor/def/edit/id/'+id+'/vid/'+data+'">'+data+'</a><a href="/ajax/globe/delete/tbl/v/id/'+data+'" class="delete" data-remove="p" data-item="v" data-action="delete"><img src="/front/images/editor/x.png" alt="Delete"></a></p></li>')
changePreviewLink(data)
'text')
# Add background layer if new variation
observeEdits()
# Save action
window.savePage = (redirect) ->
orderLayers()
variation(vid,false)
num = $('.edit').length
count = 0
# Delete deleted layers
$.post(
'/ajax/globe/delete/tbl/e/id/1/'
deleted : $('#layers').data('deleted')
)
# edits = []
# Save each edit
$('.edit').each ->
$this = $(@)
pos = $this.position()
value = buttons[$this.data 'type'].getValue.apply @
editData = {
type: $this.data('type')
zindex: $this.attr('z-index')
id : $this.data('id')
vid : window.vid
# Position data
xPos : pos.left
yPos : pos.top
width : $this.width()
height : $this.height()
html : value
css : $this.attr('style')
}
$.post(
'/editor/ajax/save/'
editData
(data) ->
$this.data('id', data)
count++
if count is num
$.loading(false,'')
if redirect
window.location = '/campaign/def/details/id/'+id
'text')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment