Skip to main content

Draft Objects

To make object creation faster, M-Files can create an object before its file content has finished uploading. The object is first created as a draft that contains empty placeholder files. When the upload completes, the file content is committed to the object and the object is marked as active, which clears the draft state.

This means that an object that appears in the listing, or that is reported by an event, is not necessarily a complete object yet.

info

Draft objects are introduced in M-Files version 26.11. If your application works with newly created objects, check that it handles draft objects as described on this page.

What Changed​

Before M-Files 26.11, when a new object appeared in the listing, it was already complete and contained its file content. Now an object can go through two stages:

  1. Draft. The object exists in the vault and can appear in the listing, but its files are placeholders without content. Only the title and extension of each file are known.
  2. Active. The file content has been uploaded and committed, and the draft state has been cleared. From now on the object is a normal object.

A draft object does not always become active:

Upload resultObject state
Upload completedThe file content is committed and the object becomes active.
Upload failedThe object stays a draft until the user retries the upload or deletes the object.
Upload cancelledThe draft object is deleted.
danger

Do not use draft objects. A draft object is not ready. Do not perform file operations on it, such as downloading, reading, replacing, adding, renaming, removing, or converting files. Also avoid other operations on the object, such as modifying properties, checking it in or out, or changing permissions, until the object is active.

Operations on a draft object can fail, or they can use the empty placeholder content instead of the real file.

Detecting a Draft Object​

Objects are described by the ObjectVersionEx message. The draft state is stored in the is_draft flag of the object-level ITEMFLAGS:

objectVersionEx.object_info.options.is_draft

The flag is true while the object is a draft. When the object becomes active, the flag is false or missing.

draftObjects.js
// Returns true if the object is a draft and must not be used yet.
function isDraft( objectVersionEx ) {
return objectVersionEx?.object_info?.options?.is_draft === true;
}

// Returns a key that identifies the object regardless of its version.
function getObjectKey( objectVersionEx ) {
const objId = objectVersionEx?.object_info?.obj_id;
return objId ? `${objId.type}:${objId.item_id?.internal_id}` : null;
}

The same check works anywhere you receive an ObjectVersionEx:

SourceWhere to find the ObjectVersionEx
IShellItems (Items, CurrentSelection)shellItems.ObjectVersions[i]
MFiles.Event.ListItemAdded (IShellListing)objectVersion argument
MFiles.Event.ListItemModified (IShellListing)newObjVer[i]
MFiles.Event.ObjectCreated (IShellUI)createdObject.object_version
Vault API, for example GetObjectDataOfMultipleObjectsresults[i].object_data.object_version

Finding Out When the Object Is Ready​

There is no separate UIX event for a draft that becomes active. Instead, check the object state each time you get updated information about the object, and continue only when is_draft is no longer true.

The approach depends on where your application gets the object from.

Objects in the Listing​

When a draft object becomes active, the listing item is updated and the listing raises MFiles.Event.ContentChanged. The event gives you all items in the listing, including the updated object.

The example below runs OnObjectReady for each new object only after the object is active:

  • When ListItemAdded reports an active object, the object is processed immediately.
  • When ListItemAdded reports a draft, the object is remembered and processed later, when ContentChanged shows that the object is no longer a draft.
  • When ListItemRemoved reports that the object was removed, for example because the user cancelled the upload, the object is forgotten.
main.js
function OnNewShellUI( shellUI ) {
shellUI.Events.Register( MFiles.Event.NewNormalShellFrame, shellFrame => {
shellFrame.Events.Register( MFiles.Event.NewShellListing, listing => {
ObserveNewObjects( listing );
} );
} );
}

function ObserveNewObjects( listing ) {
// Keys of draft objects in this listing that are not ready yet.
const pendingDrafts = new Set();

listing.Events.Register( MFiles.Event.ListItemAdded, objectVersionEx => {
const key = getObjectKey( objectVersionEx );
if( !key )
return;

if( isDraft( objectVersionEx ) ) {
// Not ready yet. Wait until the listing reports the object as active.
pendingDrafts.add( key );
return;
}
OnObjectReady( objectVersionEx );
} );

listing.Events.Register( MFiles.Event.ContentChanged, shellItems => {
if( pendingDrafts.size === 0 )
return;

for( const objectVersionEx of shellItems.ObjectVersions || [] ) {
const key = getObjectKey( objectVersionEx );
if( pendingDrafts.has( key ) && !isDraft( objectVersionEx ) ) {
pendingDrafts.delete( key );
OnObjectReady( objectVersionEx );
}
}
} );

listing.Events.Register( MFiles.Event.ListItemRemoved, listItem => {
const removedItems = Array.isArray( listItem ) ? listItem : [ listItem ];
removedItems.forEach( item => pendingDrafts.delete( getObjectKey( item ) ) );
} );
}

async function OnObjectReady( objectVersionEx ) {
// The object is active and its files contain the real content.
// It is now safe to perform file and other operations on the object.
}
tip

To react only to objects that the user has selected, use MFiles.Event.SelectionChanged and MFiles.Event.SelectedItemsChanged. SelectedItemsChanged is raised when a selected item is modified, which includes a selected draft that becomes active.

Objects Reported by the ObjectCreated Event​

MFiles.Event.ObjectCreated of IShellUI is raised as soon as the object has been created on M-Files Server. With draft object creation, the object might still be a draft when this event is raised.

If you also need the object outside the listing, for example in a background task, read the latest state of the object from the vault until it is no longer a draft. Poll with a reasonable interval and stop after a timeout, because the upload can fail or be cancelled, and then the object never becomes active.

main.js
function OnNewShellUI( shellUI ) {
shellUI.Events.Register(
MFiles.Event.ObjectCreated,
async ( isVisibleToUser, createdObject ) => {
const objectVersionEx = createdObject?.object_version;
if( !objectVersionEx )
return;

const readyObject = isDraft( objectVersionEx )
? await WaitUntilObjectIsReady( shellUI, objectVersionEx.object_info.obj_id )
: objectVersionEx;

if( readyObject )
await OnObjectReady( readyObject );
}
);
}

// Returns the active object, or null if the object was not ready before the timeout
// or could not be read (for example, the draft was deleted).
async function WaitUntilObjectIsReady( shellUI, objId, intervalMs = 3000, timeoutMs = 10 * 60 * 1000 ) {
const deadline = Date.now() + timeoutMs;
while( Date.now() < deadline ) {
await new Promise( resolve => setTimeout( resolve, intervalMs ) );

const response = await shellUI.Vault.ObjectOperations.GetObjectDataOfMultipleObjects( {
obj_vers: [ { obj_id: objId } ],
data_request: {
required_data_flags: { object_version: true },
version_type: MFiles.VaultEnums.ObjectVersionDataRequestVersionType
.OBJECT_VERSION_DATA_REQUEST_VERSION_TYPE_LATEST_VERSIONS
}
} );

const result = response?.results?.[ 0 ];
if( !result || result.error_info )
return null;

const objectVersionEx = result.object_data?.object_version;
if( objectVersionEx && !isDraft( objectVersionEx ) )
return objectVersionEx;
}
return null;
}
info

If the object is in the active listing, prefer the listing events described in Objects in the Listing. They do not need extra requests to M-Files Server.

Custom Commands​

If your application adds custom commands that operate on the selected objects, hide them while the selection contains draft objects. Update the command state again when the selection changes or when the selected items are modified.

main.js
async function UpdateCommandState( shellFrame, commandId, shellItems ) {
const objectVersions = shellItems?.ObjectVersions || [];
const isReady = objectVersions.length > 0 && !objectVersions.some( isDraft );
const state = isReady ? MFiles.CommandState.CommandState_Active : MFiles.CommandState.CommandState_Hidden;

await shellFrame.Commands.SetCommandState( commandId, MFiles.CommandLocation.MainMenu, state );
await shellFrame.Commands.SetCommandState( commandId, MFiles.CommandLocation.ContextMenu, state );
}

// Call these registrations in the MFiles.Event.NewShellListing handler.
listing.Events.Register( MFiles.Event.SelectionChanged,
shellItems => UpdateCommandState( shellFrame, commandId, shellItems ) );
listing.Events.Register( MFiles.Event.SelectedItemsChanged,
shellItems => UpdateCommandState( shellFrame, commandId, shellItems ) );

Also check the draft state in the command handler itself, because the object state can change between the moment the command was enabled and the moment the user clicks it.

Summary​

  • Starting from M-Files 26.11, an object can exist as a draft with empty placeholder files while its file content is still uploading.
  • Draft objects are not ready. Do not perform file operations or other operations on them.
  • Check object_info.options.is_draft of the ObjectVersionEx before you use an object.
  • For objects in the listing, wait for MFiles.Event.ContentChanged (or MFiles.Event.SelectedItemsChanged for selected objects) to report the object with is_draft cleared.
  • For objects outside the listing, read the latest object state from the vault until the draft state is cleared, and use a timeout.
  • A draft can stay a draft (failed upload) or be removed (cancelled upload). Handle both cases.

See Also​