0

I'm working on adding some image editing tools using the Pixastic library. The idea is that the user can choose an aspect of the image or tool they want from a select box, then the tool will show up below the select box (I'm using select2) and the user can edit via a slider. Here's what I have so far:

# This seeds the select2 options list   
imageToolsList = [
    {id: 'bl', text: 'Blur'}
    {id: 'bc', text: 'Brightness/Contrast'}
    {id: 'ca', text: 'Color Adjust (RGB)'}
    ...
]

#Creates a select box and calls imageTooler function when the value of the box is changed
$(".image_tools_select").each ->
    $(@).select2
        placeholder: "Select an adjustment tool."
        data: imageToolsList
    $(@).on("change", (i) ->
        imageTooler JSON.stringify(
            val: i.val
            clipName: $(@).closest('.clip').attr('id')
        )
    )

# The function called on the value that the select box is changed to
imageTooler = (i) ->
    imageData = jQuery.parseJSON(i)
    iId = imageData.val
    imageClipName = imageData.clipName
    newTool = "<div id=#{iId}><label>#{iId}</label><div class='slider#{iId}'></div></div>"
    $("##{imageClipName}").find(".imagetoolfields").append newTool

This succeeds in appending the name of the editing tool and the correct slider div beneath the select box when a tool is chosen, but what I'd really like is dynamically create a slider function for that particular tool and image (there are multiple images on a page, each with their own editing toolbelt). Here's a slider function that works for a the 'Blur' tool:

$('.sliderbl').slider
    min: 0
    max: 5
    value: 0.5
    step: 0.1
    range: "min"
    slide: (event, ui) ->
        $("#img_snapshot_16").pixastic("blurfast", {amount:ui.value})

Is there a way to expand the imageToolsList so that it looks something like:

imageToolsList = [
    {id: 'bl', text: 'Blur', tool: $("##{imageClipName}").pixastic("blurfast", {amount:ui.value}), sliderVals: {min: 0, max: 5, value: 0.5, step: 0.1, range: "min"} }
    ...
]

and then dynamically create the jQuery slider functions for each tool in imageTooler, as is being done with the div and slider div?

Michael
  • 683
  • 9
  • 21
  • So you want to start with something like your `imageToolsList` and end up with your `$('.sliderbl').slider(...)` call? – mu is too short Nov 02 '12 at 21:22
  • @muistooshort Yes--I'm just not sure how to grab the information from imageToolsList (or something like it) and create a `$('.sliderbl').slider(...)` call from the imageTooler function that will work (I tried a few things, but ran into the limitations of my coffeescript/javascript abilities). I suppose I could write out every single `$(XXX).slider` call myself but was hoping to find a DRYer/more maintainable way to go about this. – Michael Nov 02 '12 at 22:55
  • If you turn `tool` into a function (or a function which returns a function so that you can supply `imageClipName`) then you'd be pretty much there, no? – mu is too short Nov 03 '12 at 01:38
  • You mean turn the list into a function? Or to just make the "tool" value in the list the full slider function? What's the easiest way to do that without having to rewrite the slider function each time? Sorry for being a bit dense--I'm coming at javascript from a Ruby background, so I often find that things that seem like they should work in a certain way just don't fly in js. – Michael Nov 03 '12 at 03:52
  • 1
    `tool: -> $(...)...` or `tool: (imageClipName) -> -> $("##{imageClipName}")....`. It has to be a function or it will be executed when `imageToolsList` is being built, just like `h = { :k => fn() }` vs `h = { :k => lambda { fn() } }` in Ruby. – mu is too short Nov 03 '12 at 03:55
  • I see--makes sense. How would I then "call" the `tool:` function? One thing I'm not grasping about Coffeescript/JS is how to lookup a particular item on the `imageToolsList` using a specific `id` and then calling a specific item from the list, like `tool:`, within in the `imageToolsList` function. – Michael Nov 03 '12 at 12:42

1 Answers1

1

Comments get a little tedious for anything complicated so I'll just go ahead and map it all out. I've made a few assumptions about what is defined where and when but I don't think the assumptions matter that much.

We'll start with a simplified case: just one object similar to what you have in imageToolsList:

{
    id: 'bl'
    text: 'Blur'
    sliderVals: { min: 0, max: 5, value: 0.5, step: 0.1, range: "min" }
    tool: (imageClipName) ->
        (event, ui) -> $("##{imageClipName}").pixastic("blurfast", {amount:ui.value})
}

I've tweaked the order a little bit and switched tool to a function which returns a function. We don't want the pixastic call to happen while you're defining the object literals in imageToolsList, making tool a function allows us to defer the pixastic execution until later. Since we (presumably) don't know what imageClipName should be when we define imageToolsList, we need another function to allow us to fill that in with, again, calling pixastic until even later; hence the function returning a function trick.

Given one of these, how do we build a slider call? All we need to do is copy sliderVals (to avoid changing imageToolsList) and fill in the slide function:

sliderDef = { id: 'bl', ... }
doTheSliderThing = (imageClipName) ->
    slide = sliderDef.tool(imageClipName)
    args  = $.extend({ }, sliderDef.sliderVals, slide: slide)
    $(".slider#{sliderDef.id}").slider(args)

# And make it all go and pixastic-ify `#pancakes`.
doTheSliderThing('pancakes')

tool is a function which returns a callback function so sliderDef.tool(imageClipName) gives us the appropriate

(event, ui) -> $(...).pixastic(...)

callback function.

If we have an id and we want the appropriate entry from imageToolList, then we have to go looking for it:

# If the list is short:
[sliderDef] = (o for o in imageToolList when o.id == id)

The for loop gives you an array back and then the [sliderDef] unwraps that array and leaves the single result in sliderDef. If the imageToolList is longer then you'd want to short-circuit the loop and bail out as soon as you have a result:

# Longer list, bail out as soon as we've found what we're looking for.
for o in imageToolList when o.id == 2
    sliderDef = o
    break

or better, rework the structure of imageToolList to allow direct access by id:

# Even longer list: allow direct access by `id`.
imageToolList =
    bl: { text: 'Blur', sliderVals: { ... }, ... }
    ...

and then we can do things like this:

doTheSliderThing = (id, imageClipName) ->
    sliderDef = imageToolList[id]
    slide     = sliderDef.tool(imageClipName)
    args      = $.extend({ }, sliderDef.sliderVals, slide: slide)
    $(".slider#{id}").slider(args)

# And make it all go and pixastic-ify `#pancakes` using `'bl'`.
doTheSliderThing('bl', 'pancakes')

Or, if you prefer to be terse:

doTheSliderThing = (id, imageClipName) ->
    $(".slider#{id}").slider($.extend({ }
        imageToolList[id].sliderVals
        slide: imageToolList[id].tool(imageClipName)
    ))

Update for the comments: If you have this:

sliderDefs = 
    bl: { text: 'Blur', sliderVals: { ... }, ... }
    ...

Then you can build the stuff that slider2 wants like this:

opts = ({id: k, text: v.text} for k,v of sliderDefs)
mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • Works like a dream! The only thing I had to change was to have two lists, `imageToolsList = [{id: 'bl', text: 'Blur'},{id: 'bc', text: 'Brightness/Contrast'}...]` and `sliderDef = bl: {sliderVals: { min: 0, max: 5, value: 0.5, step: 0.1, range: "min" }, tool: (imageClipName) -> (event, ui) -> $("##{imageClipName}").pixastic("blurfast", {amount:ui.value})}`, because select2 was specifically expecting a `data` element formatted like `[{id: 'bl', text: 'Blur'}...]`. Is there some way to dynamically create that imageToolsList from the sliderDef list if I add a `text:` value to it? – Michael Nov 04 '12 at 14:27