image

by goldfingerxyz

HTML

<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout/2.2.1/knockout-min.js"></script>
<link rel="stylesheet" href="http://twitter.github.io/bootstrap/assets/css/bootstrap.css">
<div class="container">
    <div class="row">
        <div class="span">
           <h2>Image choices</h2>     
        </div>
           
    </div>
    <div class="row images" >
        <div class="span" data-bind="foreach: images">
            <a href="#" data-bind="css: { selected: $parent.selectedImage() === $data }">
              <img data-bind="click: $parent.selectedImage, attr: { src: url, title: name }" />  
            </a>
        </div>
    </div>
    <div class="row">
        <div class="span4">
            <div class="editor" data-bind="paper: true, images: { data: images, click: selectedImage }"></div>
        </div>
        <div class="tools span4" data-bind="with: selectedImage">
            <div class="row">
                <h3>Move (<span data-bind="text: x() + ',' + y()"></span>)</h3>
            </div>
            <div class="row move">
                <i class="icon icon-arrow-up" data-bind="click: moveUp"></i>
                <i class="icon icon-arrow-left" data-bind="click: moveLeft"></i>
                <i class="icon icon-arrow-right" data-bind="click: moveRight"></i>
                <i class="icon icon-arrow-down" data-bind="click: moveDown"></i>
            </div>
            <div class="row">
                 <h3>Rotate (<span data-bind="text: rotation"></span>)</h3>
            </div>
            <div class="row rotate">
                <i class="icon icon-repeat" data-bind="click: turnCounterClockwise"></i>
                <i class="icon icon-undo" data-bind="click: turnClockwise"></i>
            </div>
            <div class="row">
                 <h3>Size (<span data-bind="text: scale().toFixed(1)"></span>)</h3>
            </div>
            <div...

CSS

@import "//netdna.bootstrapcdn.com/font-awesome/3.0/css/font-awesome.css";

.container { margin-left: 50px; }

.tools { padding-left: 20px; }

.tools .icon {
    font-size: 2em;
    position: absolute;
    cursor: pointer;  
    width: 32px;
}

.move, .rotate, .size { 
    position: relative; 
    height: 30px;
}  
.move { height: 60px; }

.icon-arrow-up { left: 20px; top: -15px; }
.icon-arrow-left { left: 0; top: 6px; }
.icon-arrow-right { left: 42px; top: 6px; }
.icon-arrow-down { left: 20px; top: 26px; }
 
.icon-minus { top: -10px; }
.icon-plus { left: 36px; top: -10px; }

.icon-undo { top: -10px; }
.icon-repeat { left: 36px; top: -10px; }
  
 .icon:hover { color: blue; }

.editor {
  background-color: #ccc;
  height: 300px;
  width: 300px;
}

.editor image {
    cursor: pointer;
}

.images { padding-bottom: 10px; }

.images .selected img { border: solid 2px green; }

.images img { 
    width: 60px; 
    padding: 5px;
    border: solid 2px #ccc;
}

.images img:hover { border: solid 2px blue; }

.links { display: none; }

JavaScript

ko.bindingHandlers.paper = {
  init: function(element, valueAccessor) {
     var paper = Raphael(element); 
     ko.utils.domData.set(element, "paper", paper);
  }
};

ko.bindingHandlers.images = {
    init: function(element, valueAccessor, allBindings, vm, context) {
       var options = ko.utils.unwrapObservable(valueAccessor()),
           images = options && ko.utils.unwrapObservable(options.data);
        
        ko.utils.arrayForEach(images || [], function(image) {
          ko.applyBindingsToNode(element, { 
            image: { 
                    data: image, 
                    click: options.click 
                } 
            }, context);
         });
    }
};

ko.bindingHandlers.image = {
  init: function(element, valueAccessor, allBindings, vm, context) {
     var paper = ko.utils.domData.get(element, "paper"),
         image = paper.image("", 0, 0, 50, 50),
         options = ko.utils.unwrapObservable(valueAccessor()),
         data = options.data;

     if (typeof options.click === "function") {
        ko.applyBindingsToNode(image[0], { 
            click: options.click
        }, context.createChildContext(ko.utils.unwrapObservable(options.data)))
     }
      
     //set up computed to separately process url
    ko.computed({
      read: function() {
         var imageData = ko.utils.unwrapObservable(data),
             url = imageData && ko.utils.unwrapObservable(imageData.url);

         image.attr("src", url);
      },
      disposeWhenNodeIsRemove: element
    });
    
    //separate computed to process transform information
    ko.computed({
      read: function() {
         var imageData = ko.utils.unwrapObservable(data),
             rotation, scale, x, y;

        if (imageData) {
          rotation = ko.utils.unwrapObservable(imageData.rotation) || 0;
          scale = ko.utils.unwrapObservable(imageData.scale) || 1;
          
          x = ko.utils.unwrapObservable(imageData.x) || 0;
          y =...