Find the appropriate DOM element

2019-07-24 02:26发布

Here follows my model:

<!-- ko foreach: teamMembers -->
    <tr>
      <!-- ko foreach: days -->   
      <td>
        <!-- ko foreach: shifts -->
          <input type="text" data-bind="value: startTime">
          <input type="text" data-bind="value: endTime">          
       <!-- /ko -->
      </td>
      <!-- /ko -->       
    </tr>
<!-- /ko -->

and my viewmodel:

function TimeCardViewModel() {
    var self = this;
    self.teamMembers=ko.observableArray();
  }

function TeamMemberViewModel(data){
    var self=this;
    self.days=ko.observableArray();
    for (var i=0; i<7; i++)  {
      self.days.push(new DayViewModel(...);
    }
  }

function DayViewModel(shifts){
    var self=this;
    self.shifts=ko.observableArray();
    for (var i=0; i<shifts.length; i++)  {
       self.shifts.push(new ShiftElementsViewModel(...);
    } 
  }

function ShiftElementsViewModel(a,b,c,d) {
    var self=this;
    self.startTime=ko.observable(a);
    self.endTime=ko.observable(b);
  }

var timeCardViewModel=new TimeCardViewModel();
ko.applyBindings(timeCardViewModel);

For each member, we have (for each day of the seven days of the week) a number of shifts. For each shift, we have pairs of startTime-endTime inputs. As far as the visual result is concerned, there are rows which include all the weekly shifts of a member and it might be the case of multiple shifts per day per member. If we look at the columns, these include all the shifts for all the members for a certain day.
My great problem is that I want, whenever there is a blur event on the DOM element of endTime, to focus on the DOM element of startTime vertically. For example, if we are on Monday and the first member has two shifts I want to focus on the startTime of the second shift of the first member when blur is occurring to the endTime of the first shift and then on the startTime of the first shift on Monday of the second member when blur is occurring to the endTime of the second shift of the first member. The same for Tuesday etc. How may I achieve that? For the time being, the cursor is travelling horizontally.

3条回答
等我变得足够好
2楼-- · 2019-07-24 02:54

This should work for you...

jQuery(function($) {
  $('body').on("blur", "input[data-bind*='value: endTime']", function() {
    var
      $t = $(this), // Current input
      $td = $t.closest('td'), // Current input's parent td
      i = $td.find('input[data-bind*="value: endTime"]').index($t), // Index of current input = current shift index
      $target = $td.find('input[data-bind*="value: startTime"]').eq(i + 1); // Target is current shift + 1

    if ($target.length) {
      $target.focus();
    }
  });
});

The idea is to bind an event handler to blur event of every input that contains value: endTime in data-bind attribute.
If this handler, we find out the index of endTime input in day, add 1 to it and focus the startTime input with that index in the same td (day)

查看更多
虎瘦雄心在
3楼-- · 2019-07-24 02:56

My advice would be to use a computed tabindex attribute.

  • In your $root viewmodel, we'll compute an array of shift.start and end observables ordered like you want them to be in terms of focus.
  • We'll also make a factory method that returns a computed index for each <input> bind.
  • Each input gets an attr: { 'tabindex': getTabIndex() } binding that makes sure the index stays up to date

This approach will work for people that use the tab key to navigate through the form. Most importantly; now that you have a computed list of sorted input observables, you can easily bind to events to select a previous/next one.

Here's an example:

var Root = function() {
  this.members = ko.observableArray([]);

  for (var i = 0; i < 5; i += 1) {
    this.members.push(new Member());
  }

  // Note: you could do this in less lines of code, but I wanted
  // to be extra transparent to show the structure of the data
  // transform.
  var orderedShiftInputs = ko.pureComputed(function() {
    // We want the days to be leading, so we create a
    // two-dimensional array: [[meber1day1, member2day1], [member1day2], etc]
    // Note: members _cannot_ skip days
    var mergedDays = [];
    this.members().forEach(function(member) {
      member.days().forEach(function(day, index) {
        if (!mergedDays[index]) {
          mergedDays[index] = [];
        }

        mergedDays[index] = mergedDays[index].concat(day);
      });
    });

    // We flatten the 2d-array of days to a list of shifts:
    // [member1day1shift1, member1day1shift2, member2day1shift1, etc]
    var mergedShifts = mergedDays.reduce(function(shifts, days) {
      var allShifts = days.reduce(function(all, day) {
        return all.concat(day.shifts());
      }, []);
      return shifts.concat(allShifts);
    }, []);

    // We flatten the shifts into an array of start and end observables:
    // [member1day1shift1start, member1day1shift1end, etc.]
    return mergedShifts.reduce(function(inputs, shift) {
      return inputs.concat([shift.start, shift.end]);
    }, []);

  }, this);
  
  this.getTabIndex = function(data) {
    return ko.computed(function() {
      // Find the start or end data in our sorted array.
      // In this example, we can start with index 1. In your app,
      // there might be other input elements before the shifts...
      var START_TAB_INDEX = 1;
      return orderedShiftInputs().indexOf(data) + START_TAB_INDEX;
    }, this);
  }.bind(this);

}

var Member = function() {
  this.days = ko.observableArray([]);

  for (var i = 0; i < 2; i += 1) {
    this.days.push(new Day());
  }

}
var Day = function() {
  this.shifts = ko.observableArray([]);

  for (var i = 0; i < 3; i += 1) {
    this.shifts.push(new Shift());
  }
}

var Shift = function() {
  this.start = ko.observable(1);
  this.end = ko.observable(2);
}

ko.applyBindings(new Root());
td {
  border: 1px solid black
}
input {
  display: inline;
  width: 30px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<table>
  <tbody>
    <!-- ko foreach: members -->
    <tr>
      <td data-bind="text: 'Member-' + $index()"></td>
      <!-- ko foreach: days -->
      <td>
        <span data-bind="text: 'Day ' + $index()"></span>
        <br/>
        <!-- ko foreach: shifts -->
        <input type="text" data-bind="value: start, 
                                      attr: { 'tabindex': $root.getTabIndex(start) }">
        <input type="text" data-bind="value: end, 
                                      attr: { 'tabindex': $root.getTabIndex(end) }">
        <!-- /ko -->
      </td>
      <!-- /ko -->
    </tr>
    <!-- /ko -->
  </tbody>
</table>

查看更多
冷血范
4楼-- · 2019-07-24 03:01

Here i am showing you one of the approaches by using all knockout. It shows you the logic how to implement yours since I don't have your data sample and you might need to modify it based on your data

Working example : https://jsfiddle.net/kyr6w2x3/48/

HTML:

<table>
 <tbody>
   <!-- ko foreach: teamMembers -->
    <tr>
    <td data-bind="text:name"></td>
      <!-- ko foreach: days -->   
      <td>
      <h4 data-bind="text:name"></h4>
          <input type="text" data-bind="value: startTime ,hasFocus :getFocus">
          <input type="text" data-bind="value: endTime ,event:{blur: $root.endTimeBlur}">          

      </td>
      <!-- /ko -->       
    </tr>
<!-- /ko -->
 </tbody>
</table>

JS:

var data ={
    teamMembers: [{
        Name: "Member A",
    id:1,
        days: [{startTime: '8:00',endTime: "4:00" ,name:'Monday',parentId:1},{startTime: '8:00',endTime: "4:00",name:'Tuesday',parentId:1},{startTime: '8:00',endTime: "4:00",name:'Wednesday',parentId:1},{startTime: '8:00',endTime: "4:00",name:'Thursday',parentId:1},{startTime: '8:00',endTime: "4:00",name:'Friday',parentId:1},{startTime: '8:00',endTime: "4:00",name:'Saturday',parentId:1},{startTime: '8:00',endTime: "4:00",name:'Sunday',parentId:1}]
    },
                   {
        Name: "Member B",
    id:2,
        days: [{startTime: '8:00',endTime: "4:00" ,name:'Monday' ,parentId:2},{startTime: '8:00',endTime: "4:00",name:'Tuesday' ,parentId:2},{startTime: '8:00',endTime: "4:00",name:'Wednesday',parentId:2},{startTime: '8:00',endTime: "4:00",name:'Thursday',parentId:2},{startTime: '8:00',endTime: "4:00",name:'Friday',parentId:2},{startTime: '8:00',endTime: "4:00",name:'Saturday',parentId:2},{startTime: '8:00',endTime: "4:00",name:'Sunday',parentId:2}]
    },
  {
        Name: "Member C",
    id:3,
        days: [{startTime: '8:00',endTime: "4:00" ,name:'Monday',parentId:3},{startTime: '8:00',endTime: "4:00",name:'Tuesday',parentId:3},{startTime: '8:00',endTime: "4:00",name:'Wednesday',parentId:3},{startTime: '8:00',endTime: "4:00",name:'Thursday',parentId:3},{startTime: '8:00',endTime: "4:00",name:'Friday',parentId:3},{startTime: '8:00',endTime: "4:00",name:'Saturday',parentId:3},{startTime: '8:00',endTime: "4:00",name:'Sunday',parentId:3}]
    },]

}
var memberViewModel = function(data) {
 var self = this ;
 self.days = ko.observableArray([]);
 self.name = ko.observable(data.Name);
 self.id = ko.observable(data.id); 
 self.days($.map(data.days, function (item) {
    return new daysViewModel(item);
 }));

}
var daysViewModel = function (data){
 var self = this ;
 self.getFocus = ko.observable(false);
 self.startTime = ko.observable(data.startTime);
 self.endTime = ko.observable(data.endTime);
 self.name = ko.observable(data.name)
 self.parentId = ko.observable(data.parentId);
}

function ViewModel() {
  var self = this;
  self.teamMembers = ko.observableArray([]);
  self.teamMembers($.map(data.teamMembers, function (item) {
    return new memberViewModel(item);
  }));
  self.endTimeBlur = function(data){
    ko.utils.arrayFirst(self.teamMembers(), function (item,i) {
      if (item.id() == data.parentId() && self.teamMembers()[i+1] ) {
        //here you set getFocus to true to make next member 's monday gets focus    
        self.teamMembers()[i+1].days()[0].getFocus(true);
        return;
      }
    });
  }
}  
  ko.applyBindings(new ViewModel());
查看更多
登录 后发表回答