Premium support for our pure JavaScript UI components


Post by mlaukkanen »

Hi,

I'm struggling to find the cause of an issue that occurs sometimes after creating a new task. In summary:

When creating a new task with the + at the bottom of the column, the error below is sometimes reported in the console.

Unhandled rejection in promise: Cannot read properties of null (reading 'closest')
LogHelper.promiseRejectionUnhandled @ LogHelper.ts:45
LogHelper.ts:46 TypeError: Cannot read properties of null (reading 'closest')
at TaskBoard.resolveTaskItem (taskboard.umd.js:38631:31)
at SimpleTaskEdit.editTask (taskboard.umd.js:35060:20)
at TaskBoard.addTask (taskboard.umd.js:38344:234)

SimpleTaskEdit feature is enabled and that triggers the error because what is actually happening is your SimpleTaskEdit.js file the following code doesn't find the element:

editTask(taskRecord, element) {
    const
        me        = this,
        taskBoard = me.client;

// Get element from record if none supplied
if (!element) {
    element = taskBoard.getTaskElement(taskRecord);
}
...

Specifically taskBoard.getTaskElement(taskRecord) returns null.

The reason for this I have found to be that the task html element still has the $PhantomId as part of the div id property and not the correct server assigned guid that was returned from the sync.

This only happens sometimes, so I'm guessing there is an async related cause why the task card doesn't get updated in time for the subsequent edit operation. But I'm lost after hours digging though your code and mine as what could be wrong on my side to cause this.

One last thing, the SimpleTaskEdit is not the cause as if that is disabled then no exception will occur but the task card will not refresh on the board correctly: specifically server side properties that are set on new tasks will not be shown immediately (only sometimes again though!).

Thanks for you support,
Martin


Post by alex.l »

Hi Martin,

What version are you currently using? Try to upgrade to the latest and let us know if you still have that problem!

All the best,
Alex


Post by mlaukkanen »

Hi Alex,

I did that today actually so I'm now running: 4.3.6


Post by alex.l »

Thank you for investigation and your report, we will check that. Here is a link to the ticket: https://github.com/bryntum/support/issues/4081

All the best,
Alex


Post by Animal »

Are you using CrudManager's autoSync: true? CrudManager suspends autoSync on dataready. How is the sync happening which updates the id before the edit feature? This does not happen normally. We need to see the code.


Post by mlaukkanen »

Hi,
Yes I do use autoSync: true, I'll try to provide some relevant code here for you.

I have implemented an extended ProjectModel, here is our model's sync implementation:

public async sendRequest(request: Request): Promise<any> {
  switch (request.type) {
    case 'load':
      return await this.loadData(request);
    case 'sync':
      return await this.syncData(request);
  }
}

private async syncData(request: Request): Promise<Response> {
  const response = new Response();

  if (request.data.tasks.added) {
    const updates = await this.getTaskAddedPackage(request.data.tasks.added);
    const updated = await this.apiService.addRecords(updates);
    response.addTaskRows(updated);
  }
  // code removed here
  return response;
}

private async getTaskAddedPackage(changes: any[]): Promise<UpdateAction[]> {
  const updates: UpdateAction[] = [];

  // Get the changes from the taskStore with the original records
  const tasks: any = this.taskStore.changes || { added: [], modified: [], removed: [] };

  // eslint-disable-next-line no-restricted-syntax
  for (const change of changes) {
    // Clone change and clean up data fields we will send to server
    let data = { ...change };
    const record = tasks.added.find((t: TaskModel) => t.id === change.$PhantomId);

// Generate a GUID for this item
data[this.idField] = uuidv4();

// code removed here

const update = new UpdateAction(record, data, this.taskEntity);
updates.push(update);
  }

  return updates;
}

Sorry if that's too much but I wanted to give you the full picture (without the other 200 lines in that model).

One thing to note there is that we assign our backend UID near the end there using uuidv4(), that was from our original (Gantt) implementation to support the task / dependency / resource / etc links during creation / update.

Then in the syncData method we call our ApiService which; sends the request(s) in a batch to the backend, then returns the full representation of the data from the backend. With one small addition to the data returned from the api we re-add the Phantom Id so that your code can pick it up, that code looks like this:

private parseReturnedData(result: WebApiClient.BatchResponse, actions: UpdateAction[]) {
  // Match up the request results using the item uid
  actions.forEach((action) => {

// Return the full item data from Api
const updated: any = updates.find((update: any) => update[definition.PrimaryIdAttribute]);
action.data = { ...updated };

// code removed here 

// Return phantom id for new tasks
if (action.record.isPhantom) {
  action.data.$PhantomId = action.record.id;
}
  });
}

Hopefully that's enough for you to go on (and not too much).

Thanks,
Martin


Post Reply