2013-05-16 7 views
2

Я пытаюсь понять bacon.js и FRP, поэтому попытался сделать простой пример перетаскивания, но у меня возникли проблемы с ленивой оценкой одного фрагмента кода. Когда я добавляю в поток .log(), кажется, что он выглядит и действует нормально, но если я его вынимаю, он не обновляется. Вот что я делаю:Bacon.js ленивая оценка, пример перетаскивания мышью ломается без оператора log()

// UI streams 
block_mousedown = block_el.asEventStream('mousedown').map(xyFromEvent); 
global_mousemove = html.asEventStream('mousemove').map(xyFromEvent); 
global_mouseup = html.asEventStream('mouseup'); 

// Composites 
isDragging = block_mousedown.merge(global_mouseup.map(0)); 
mouseDragging = Bacon.combineAsArray(isDragging, global_mousemove) 
    .filter(function(v){ return notZero(v[0]) }) 

mouseDeltaFromClick = mouseDragging 
    .map(getDelta) 

// Block offset when it was clicked on 
block_pos_at_mousedown = block_mousedown 
    .map(function(a,b){ return block_el.offset();}) 
    .map(function(e){ return [e.left, e.top]; }) 
    // If I remove this log(), it doesn't evaluate 
    .log(); 

// merge mouse delta with block position when clicked  
mouseDeltaAndBlockPos = mouseDeltaFromClick 
    .combine(block_pos_at_mousedown, '.concat') 
    .onValue(function(e){ 
     block_el.css({ 
      top : e[3]+e[1]+"px", 
      left : e[2]+e[0]+"px" 
     }); 
    });  

А вот jsFiddle of it

Я думаю, что я мог бы идти о все это неправильно, это даже правильный подход? Я хочу пройти через позицию блока, когда он был нажат, который должен обновиться на mousedown, но не обновляться вместе с mousemove.

ответ

2

Поведение, которое вы описываете, имеет мало общего с ленивой оценкой: корень проблемы - это порядок выполнения.

В своем коде (без log() на block_pos_at_mousedown) mouseDeltaFromClick, кажется, изменится до block_pos_at_mousedown (я должен сказать, что я не знаю, как именно log() изменяет порядок). Давайте позаботимся об этом.

observable.combine способ ожидает Property в качестве первого аргумента - EventStream, вы прошли, будет автоматически преобразовано. Теперь mouseDeltaAndBlockPos изменяет (и, следовательно, срабатывает все обратные вызовы), когда mouseDeltaFromClickилиblock_pos_at_mousedown изменений.

Так что, когда mouseDeltaFromClick пожаров до block_pos_at_mousedown выполнения обратного вызова в конце коды вызываются с новой дельтой , но со старой позицией блока (потому что back_pos_at_mousedown был преобразован в Property). Старое значение - [0,0], поэтому блок привязывается к верхнему левому углу каждого щелчка.

Как это исправить? Безопасный способ состоит в том, чтобы не принимать во внимание порядок выполнения несвязанных обратных вызовов и писать это снова с учетом этого. Я пришел с этим:

function xyFromEvent(v){ return [v.clientX, v.clientY]; } 

function getDelta(t){ 
    var a = t[1]; 
    var b = t[0]; 
    return [a[0]-b[0], a[1]-b[1]]; 
} 

function add(p1, p2) { 
    return [p1[0] + p2[0], p1[1] + p2[1]]; 
} 

$().ready(function() { 
    var block = $("#clickable-block"); 
    var html = $("html"); 

    var blockDragging = block.asEventStream('mousedown').map(true) 
          .merge(html.asEventStream('mouseup').map(false)) 
          .toProperty(false); 

    var deltas = html.asEventStream('mousemove').map(xyFromEvent).slidingWindow(2,2).map(getDelta); 

    // Just like deltas, but [0,0] when user is not dragging the box. 
    var draggingDeltas = Bacon.combineWith(function(delta, dragging) { 
     if(!dragging) { 
      return [0, 0]; 
     } 
     return delta; 
    }, deltas, blockDragging); 

    var blockPosition = draggingDeltas.scan([0,0], add); 

    blockPosition.onValue(function(pos) { 
     block.css({ 
      top : pos[1] + "px", 
      left : pos[0] + "px" 
     }); 
    }); 
}); 

И jsFiddle: http://jsfiddle.net/aknNh/25/

EDIT: В комментариях raimohanska предложил другое решение с использованием flatMap: http://jsfiddle.net/TFPge/1/

+0

ленив оценки и порядок оценки идут рука об руку. .. –

+0

Я согласен, что эта ленивая оценка вызывает неожиданное поведение, если ваш код полагается на функцию * map *, вызываемую в определенное время. Ответ Лукаша все еще выглядит довольно хорошо для меня. За исключением того, что я использовал flatMap, как в примере рисования Wolfflow: http://codepen.io/wolfflow/pen/cudiJ В Google Groups также есть связанный поток: https://groups.google.com/forum/ #! topic/baconjs/OR0C2jPCF9g – raimohanska

+0

И вот моя скрипка с flatMap: http://jsfiddle.net/TFPge/ – raimohanska

 Смежные вопросы

  • Нет связанных вопросов^_^