0

I'm trying to edit individual elements of an array. I have a JSON array like this:

[   {"idComponent":1,"left":"100px","top":"100px","idHTML":"name_div","width":"200px","height":"300px","value":"My name is"}
               ,{"idComponent":2,"left":"200px","top":"200px","idHTML":"date_div","width":"200px","height":"300px","value":"2016-Enero-12"}
               ,{"idComponent":3,"left":"300px","top":"300px","idHTML":"weigth_div","width":"200px","height":"300px","value":"1.5 KG"}
               ,{"idComponent":4,"left":"400px","top":"400px","idHTML":"talla_div","width":"200px","height":"300px","value":"23"}
               ,{"idComponent":5,"left":"500px","top":"500px","idHTML":"description_div","width":"300px","height":"300px","value":"The text"}
            ]

These are converted to observables in knockout. Each one is binded to a DIV element to display in screen. "left", "top", "height" and "width" are CSS attributes that are applied to each one.

When you click ones of the DIV contains with the mouse, I'm trying to bind the element to HTML inputs to edit the CSS values. You can see the idea in the next picture:

Mockup Image - Click here to see the image

The code is the next:

  function convertPixelsToInches(pixels){
    return pixels/96;
  }

  // Elemento regresados desde el servidor.
  var dataFromServer =
  {
     "idTemplate":"1"
    ,"components" :
      [{"idComponent":1,"left":"100px","top":"100px","idHTML":"name_div","width":"200px","height":"300px","value":"Sergio Pinto Fernandez"}
      ,{"idComponent":2,"left":"200px","top":"200px","idHTML":"date_div","width":"200px","height":"300px","value":"2016-Enero-12"}
      ,{"idComponent":3,"left":"300px","top":"300px","idHTML":"weigth_div","width":"200px","height":"300px","value":"1.5 KG"}
      ,{"idComponent":4,"left":"400px","top":"400px","idHTML":"talla_div","width":"200px","height":"300px","value":"23"}
      ,{"idComponent":5,"left":"500px","top":"500px","idHTML":"description_div","width":"300px","height":"300px","value":"Tomar dos cucharadas cada 3 hras."}
      ]
    ,"paperSize":"papersize_USLetter_portrait"
    ,"templateImage":{
      "imageUrl":"images/SolicitudLaboratorioExpress.jpg"
      ,"width":"8.5in"
      ,"height":"11in"
      ,"left":"0px"
      ,"top":"0px"
    }
  };

  function componentModel(dataComponent) {
    if (!dataComponent) {
      dataComponent = {};
    }

    var self = this;
    self.idComponent  = ko.observable(dataComponent.idComponent);
    self.left         = ko.observable(dataComponent.left);
    self.top          = ko.observable(dataComponent.top);
    self.idHTML       = ko.observable(dataComponent.idHTML);
    self.width        = ko.observable(dataComponent.width);
    self.height       = ko.observable(dataComponent.height);
    self.value        = ko.observable(dataComponent.value);
  }

  /**
   * data Json from server.
   *
   */
  function templateModel(data) {
    if (!data) {
      data = {};
    }
    var self = this;

    self.components = ExtractComponents(self, data.components, componentModel);

    self.currentSelectedComponent = ko.observable();
    self.currentSelectedComponentIndex = ko.observable(-1);

    //self.currentSelectedComponentLeft = ko.observable();
    self.currentSelectedComponentLeft = ko.computed(function(){
      var value = self.currentSelectedComponentIndex();
      console.log(typeof value);
      //value=value*1;
      console.log(value);
      if (value ) {
        return "TT";
      }

      // Get "200px y must save as 200"
      //return self.currentSelectedComponent().left;//.substring(0,data.length-2);
      return "FF";
    });
    self.currentSelectedComponentTop = ko.observable();
    self.editComponent = function(component,index){
      self.currentSelectedComponentIndex(index);
      self.currentSelectedComponent(component);

      // Binding the component to the editor.
      // ??
    };
    function bindComponentToEditor() {
      ko.applyBindings()
    }


    self.idTemplate = ko.observable(data.idTemplate);
    self.paperSize = ko.observable(data.paperSize);

    /* */
    self.paperSizeWidth = ko.observable(convertPixelsToInches(1067));
    self.paperSizeHeigth = ko.observable(convertPixelsToInches(1067));
    //
    self.templateImage_imageUrl = ko.observable(data.templateImage.imageUrl);
    self.templateImage_width = ko.observable(data.templateImage.width);
    self.templateImage_height = ko.observable(data.templateImage.height);
    self.templateImage_left = ko.observable(data.templateImage.left);
    self.templateImage_top = ko.observable(data.templateImage.top);
  }

  /**
   * parent: referencia al objeto o funcion que mando a llamar esta fucnion.
   *
   * dataArr: Array de elementos que se desea la funcion ExtractComponents haga mapeo.
   *    Ejemplo de dataArr:
   *
   *        [   {"idComponent":1,"left":"100px","top":"100px","idHTML":"name_div","width":"200px","height":"300px","value":"Sergio Pinto Fernandez"}
           ,{"idComponent":2,"left":"200px","top":"200px","idHTML":"date_div","width":"200px","height":"300px","value":"2016-Enero-12"}
           ,{"idComponent":3,"left":"300px","top":"300px","idHTML":"weigth_div","width":"200px","height":"300px","value":"1.5 KG"}
           ,{"idComponent":4,"left":"400px","top":"400px","idHTML":"talla_div","width":"200px","height":"300px","value":"23"}
           ,{"idComponent":5,"left":"500px","top":"500px","idHTML":"description_div","width":"300px","height":"300px","value":"Tomar dos cucharadas cada 3 hras."}
        ]
   *
   * modelConstructor:  funcion con que se creara un nuevo componente, es decir el modelo.
   *
   */
  function ExtractComponents(parent, dataArr, modelConstructor) {
    var components = [];
    if (dataArr == null) {
      return components;
    }

    for (var i = 0; i < dataArr.length; i++) {
      var dataArrElement = dataArr[i];
      var component = new modelConstructor(dataArrElement,parent);
      components.push(component);
    }
    return components;
  }

  ko.applyBindings(new templateModel(dataFromServer));

I have two problems:

  1. The Input for left and top values only accept integer values, the i need substring "200px" to 200 but alway i receive a error: ".left is undefined"

    self.currentSelectedComponentLeft = ko.computed(function(){ var value = self.currentSelectedComponentIndex(); console.log(typeof value); //value=value*1; console.log(value); if (value ) { return "TT"; }

      // Get "200px and save as 200"
      //return self.currentSelectedComponent().left;//.substring(0,data.length-2);
      return "FF";
    });
    
  2. The principal problem... how can I bind the DIV element when I click the element to the inputs at the right? I think I need dynamic binding, or dynamic subscription, but can't find answer to this problem of dynamic double data binding.

This is the HTML:

<div id="mainContainer">
  <div id="contentPrint_div" class="page" data-bind="css: paperSize">

    <img id="template_img" data-bind="attr: {src: templateImage_imageUrl},
      style: {width: templateImage_width, height: templateImage_height, left: templateImage_left, top: templateImage_top}">

    <div id="fieldsArea_div" class="" data-bind="foreach: components">
      <div class="field" data-bind="html: value, style: {width: width, left:left, top:top},
         attr: {id: idHTML}, click: $parent.editComponent($data,$index)"></div>
    </div>
  </div>

  <div id="toolsbar">
    <div id="toolbarPanel">
      ID template:<span data-bind='text: idTemplate'></span>

      <div id="panelMenuInfoElements_div">
        Elemento actual: <span data-bind='text: idTemplate'></span>
        Posicion
        X:<input type="text" class="form-control" id="" placeholder="x" min="0" step="0.01"
            data-bind="attr: {max: paperSizeWidth}, value: currentSelectedComponentTop">
        Y:<input type="text" class="form-control" id="" placeholder="y" min="0" step="0.01"
            data-bind="attr: {max: paperSizeHeigth}, value: currentSelectedComponentLeft">
      </div>

    </div>
    <div id="toolbarCode">
      <pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
    </div>

  </div>
</div>
batman567
  • 826
  • 2
  • 12
  • 23
Sergio
  • 441
  • 9
  • 22
  • For your px issue in the binding this should work - style: { left: left + 'px' } – dmoo Apr 23 '16 at 07:27
  • If the double data binding worked, what would it do? This isn't jQuery so you don't need to select something to bind it. Are you needing a css change, or data or what? – brianlmerritt Apr 23 '16 at 07:34
  • @dmoo good idea, but after read the user3297291 response I thing that it must be into a computed function. – Sergio Apr 23 '16 at 14:33
  • @brianlmerritt Its my first time using knockout, and is my first script using it, I will do better code the next time :). I want change the CSS. See user3297291 response. – Sergio Apr 23 '16 at 14:39

1 Answers1

0

You posted quite some code; I'm not 100% sure if I got all your questions correctly. But I tried to create an example that I think shows how to solve your problems.

  • I've used a computed to create a style object that rewrites x and y observables that are numbers, to the style binding compatible: { top: 0px, left: 0px }
  • I've used a with binding to link your <input> elements to the last clicked block.

var Block = function(name, x, y) {
  this.label = name;
  this.x = ko.observable(x);
  this.y = ko.observable(y);

  this.style = ko.computed(function() {
    return {
      top: this.y() + "px",
      left: this.x() + "px"
    }
  }, this);
}


var VM = function() {

  this.blocks = [
    new Block("Block 1", 100, 100),
    new Block("Block 2", 200, 100),
    new Block("Block 3", 300, 100),
  ];

  this.selectedBlock = ko.observable(this.blocks[0]);
};

ko.applyBindings(new VM());
.Block {
  position: absolute;
  background: red;
  padding: 1rem;
  list-style: none;
}
.Block.is-selected { background: yellow; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="Inputs" data-bind="with: selectedBlock">
  <div>
    Editing <span data-bind="text: label"></span> 
  </div>
  <div>Left:
    <input type="number" data-bind="value: x">
  </div>
  <div>Top:
    <input type="number" data-bind="value: y">
  </div>
</div>

<ul data-bind="foreach: blocks">
  <li class="Block" data-bind="
    click: $parent.selectedBlock,   
    style: style,
    text: label,
    css: { 'is-selected': $data == $parent.selectedBlock()}"></li>
</ul>
user3297291
  • 22,592
  • 4
  • 29
  • 45
  • My good!! I spend 10 hours trying to do it. Is my first time using knockout. Yes I write so much code, sorry, but I was thinking that the full code could help to understand the idea that the Block are dinamically created, I will modify my question later. – Sergio Apr 23 '16 at 14:54
  • You are a genious, each Block must own a computer object to manages its style. I will modify the Json received from the server to only numbers, e.g.: 200px to only 200. I did not know I could do this: "click: $parent.selectedBlock," this bind a Block with the inputs O.o. I don't undestand how this happend, Is automatic I guess. Thanks!! – Sergio Apr 23 '16 at 14:56
  • The snippet is part of stackoverflow? – Sergio Apr 23 '16 at 14:58
  • Glad I could help! Whenever you use a `click` binding, knockout sends the current binding context (`$data`) and the click event to the method you attach. So if you attach an observable, like `selectedBlock`, it gets called with the current block as the first argument, thereby setting a new selection. – user3297291 Apr 23 '16 at 21:19