github.com/vugu/vugu@v0.3.6-0.20240430171613-3f6f402e014b/domrender/callback-manager.go (about)

     1  package domrender
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/vugu/vugu"
     7  	js "github.com/vugu/vugu/js"
     8  )
     9  
    10  // callbackManager handles the element lifecycle related events for browser DOM elements in order to
    11  // implement vg-js-create and vg-js-populate and anything else like this.
    12  type callbackManager struct {
    13  	nextCallbackID  uint32 // large enough and representable as float64 without loss
    14  	callbackInfoMap map[uint32]callbackInfo
    15  }
    16  
    17  type callbackInfoType int
    18  
    19  const (
    20  	callbackCreate = iota + 1
    21  	callbackPopulate
    22  )
    23  
    24  type callbackInfo struct {
    25  	typ callbackInfoType    // create or populate
    26  	f   vugu.JSValueHandler // to be called when we get the js.Value
    27  	el  js.Value            // the js element
    28  	// for populate, the ID of the corresponding create callbackInfo
    29  	// (so we can grab the element from it)
    30  	createID uint32
    31  }
    32  
    33  // startRender prepares for the next render cycle
    34  func (cm *callbackManager) startRender() {
    35  	cm.nextCallbackID = 1
    36  	if l := len(cm.callbackInfoMap); l > 0 {
    37  		cm.callbackInfoMap = make(map[uint32]callbackInfo, l)
    38  	} else {
    39  		cm.callbackInfoMap = make(map[uint32]callbackInfo)
    40  	}
    41  }
    42  
    43  func (cm *callbackManager) doneRender() {
    44  }
    45  
    46  // sets the create and populate handlers and returns a callbackID for each,
    47  // if no instruction is needed for something then 0 will be returned for its ID
    48  func (cm *callbackManager) addCreateAndPopulateHandlers(create, populate vugu.JSValueHandler) (uint32, uint32) {
    49  
    50  	if create == nil && populate == nil {
    51  		return 0, 0
    52  	}
    53  
    54  	// for either one to work, we need the element reference, so we always register the first one
    55  	// (even if the create function is empty)
    56  	cid := cm.nextCallbackID
    57  	cm.nextCallbackID++
    58  	cm.callbackInfoMap[cid] = callbackInfo{
    59  		typ: callbackCreate,
    60  		f:   create,
    61  	}
    62  
    63  	// if populate is not nil then set it up too with it's own ID and it's createID pointing
    64  	// back to the other one (so it can get at the js.Value later)
    65  	var pid uint32
    66  	if populate != nil {
    67  		pid = cm.nextCallbackID
    68  		cm.nextCallbackID++
    69  		cm.callbackInfoMap[pid] = callbackInfo{
    70  			typ:      callbackPopulate,
    71  			f:        populate,
    72  			createID: cid,
    73  		}
    74  	}
    75  
    76  	return cid, pid
    77  }
    78  
    79  // callback is call when we get a callback from the render script
    80  func (cm *callbackManager) callback(this js.Value, args []js.Value) interface{} {
    81  
    82  	if len(args) < 1 {
    83  		panic(fmt.Errorf("no args passed to callbackManager.callback"))
    84  	}
    85  
    86  	cbIDVal := args[0]
    87  	cbID := uint32(cbIDVal.Int())
    88  	if cbID == 0 {
    89  		panic(fmt.Errorf("callbackManager.callback got zero callback ID"))
    90  	}
    91  
    92  	cbInfo := cm.callbackInfoMap[cbID]
    93  	switch cbInfo.typ {
    94  
    95  	case callbackCreate:
    96  		jsVal := args[1]
    97  		cbInfo.el = jsVal
    98  		cm.callbackInfoMap[cbID] = cbInfo
    99  		// f can be nil when vg-js-populate is set but vg-js-create is not,
   100  		// we still need the element to be recorded above
   101  		if cbInfo.f != nil {
   102  			cbInfo.f.JSValueHandle(jsVal)
   103  		}
   104  
   105  	case callbackPopulate:
   106  		createID := cbInfo.createID
   107  		if createID == 0 {
   108  			panic(fmt.Errorf("callbackManager.callback handling populate found createID==0"))
   109  		}
   110  		createInfo := cm.callbackInfoMap[createID]
   111  		jsVal := createInfo.el
   112  		if jsVal.IsUndefined() { // if we got all the way here, there should always be a value
   113  			panic(fmt.Errorf("callbackManager.callback handling populate got undefined jsVal"))
   114  		}
   115  		// and f should always be set, panic if not so we can track down why
   116  		if cbInfo.f == nil {
   117  			panic(fmt.Errorf("callbackManager.callback handling populate got nil JSvalueHandler"))
   118  		}
   119  		cbInfo.f.JSValueHandle(jsVal)
   120  
   121  	default:
   122  		panic(fmt.Errorf("unknown callback type %#v", cbInfo.typ))
   123  
   124  	}
   125  
   126  	return nil
   127  }