2017-05-19 22:12:53 +00:00
/ * *
* @ file
* Attaches behavior for the Quick Edit module .
*
* Everything happens asynchronously , to allow for :
* - dynamically rendered contextual links
* - asynchronously retrieved ( and cached ) per - field in - place editing metadata
* - asynchronous setup of in - place editable field and "Quick edit" link .
*
* To achieve this , there are several queues :
* - fieldsMetadataQueue : fields whose metadata still needs to be fetched .
* - fieldsAvailableQueue : queue of fields whose metadata is known , and for
* which it has been confirmed that the user has permission to edit them .
* However , FieldModels will only be created for them once there ' s a
* contextual link for their entity : when it ' s possible to initiate editing .
* - contextualLinksQueue : queue of contextual links on entities for which it
* is not yet known whether the user has permission to edit at >= 1 of them .
* /
( function ( $ , _ , Backbone , Drupal , drupalSettings , JSON , storage ) {
2017-07-06 06:21:40 +00:00
const options = $ . extend ( drupalSettings . quickedit ,
2017-05-19 22:12:53 +00:00
// Merge strings on top of drupalSettings so that they are not mutable.
{
strings : {
2017-07-06 06:21:40 +00:00
quickEdit : Drupal . t ( 'Quick edit' ) ,
} ,
} ,
2017-05-19 22:12:53 +00:00
) ;
/ * *
* Tracks fields without metadata . Contains objects with the following keys :
* - DOM el
* - String fieldID
* - String entityID
* /
2017-07-06 06:21:40 +00:00
let fieldsMetadataQueue = [ ] ;
2017-05-19 22:12:53 +00:00
/ * *
* Tracks fields ready for use . Contains objects with the following keys :
* - DOM el
* - String fieldID
* - String entityID
* /
2017-07-06 06:21:40 +00:00
let fieldsAvailableQueue = [ ] ;
2017-05-19 22:12:53 +00:00
/ * *
* Tracks contextual links on entities . Contains objects with the following
* keys :
* - String entityID
* - DOM el
* - DOM region
* /
2017-07-06 06:21:40 +00:00
let contextualLinksQueue = [ ] ;
2017-05-19 22:12:53 +00:00
/ * *
* Tracks how many instances exist for each unique entity . Contains key - value
* pairs :
* - String entityID
* - Number count
* /
2017-07-06 06:21:40 +00:00
const entityInstancesTracker = { } ;
2017-05-19 22:12:53 +00:00
/ * *
*
* @ type { Drupal ~ behavior }
* /
Drupal . behaviors . quickedit = {
2017-07-06 06:21:40 +00:00
attach ( context ) {
2017-05-19 22:12:53 +00:00
// Initialize the Quick Edit app once per page load.
$ ( 'body' ) . once ( 'quickedit-init' ) . each ( initQuickEdit ) ;
// Find all in-place editable fields, if any.
2017-07-06 06:21:40 +00:00
const $fields = $ ( context ) . find ( '[data-quickedit-field-id]' ) . once ( 'quickedit' ) ;
2017-05-19 22:12:53 +00:00
if ( $fields . length === 0 ) {
return ;
}
// Process each entity element: identical entities that appear multiple
// times will get a numeric identifier, starting at 0.
2017-07-06 06:21:40 +00:00
$ ( context ) . find ( '[data-quickedit-entity-id]' ) . once ( 'quickedit' ) . each ( ( index , entityElement ) => {
2017-05-19 22:12:53 +00:00
processEntity ( entityElement ) ;
} ) ;
// Process each field element: queue to be used or to fetch metadata.
// When a field is being rerendered after editing, it will be processed
// immediately. New fields will be unable to be processed immediately,
// but will instead be queued to have their metadata fetched, which occurs
// below in fetchMissingMetaData().
2017-07-06 06:21:40 +00:00
$fields . each ( ( index , fieldElement ) => {
2017-05-19 22:12:53 +00:00
processField ( fieldElement ) ;
} ) ;
// Entities and fields on the page have been detected, try to set up the
// contextual links for those entities that already have the necessary
// meta- data in the client-side cache.
2017-07-06 06:21:40 +00:00
contextualLinksQueue = _ . filter ( contextualLinksQueue , contextualLink => ! initializeEntityContextualLink ( contextualLink ) ) ;
2017-05-19 22:12:53 +00:00
// Fetch metadata for any fields that are queued to retrieve it.
2017-07-06 06:21:40 +00:00
fetchMissingMetadata ( ( fieldElementsWithFreshMetadata ) => {
2017-05-19 22:12:53 +00:00
// Metadata has been fetched, reprocess fields whose metadata was
// missing.
_ . each ( fieldElementsWithFreshMetadata , processField ) ;
// Metadata has been fetched, try to set up more contextual links now.
2017-07-06 06:21:40 +00:00
contextualLinksQueue = _ . filter ( contextualLinksQueue , contextualLink => ! initializeEntityContextualLink ( contextualLink ) ) ;
2017-05-19 22:12:53 +00:00
} ) ;
} ,
2017-07-06 06:21:40 +00:00
detach ( context , settings , trigger ) {
2017-05-19 22:12:53 +00:00
if ( trigger === 'unload' ) {
deleteContainedModelsAndQueues ( $ ( context ) ) ;
}
2017-07-06 06:21:40 +00:00
} ,
2017-05-19 22:12:53 +00:00
} ;
/ * *
*
* @ namespace
* /
Drupal . quickedit = {
/ * *
* A { @ link Drupal . quickedit . AppView } instance .
* /
app : null ,
/ * *
* @ type { object }
*
* @ prop { Array . < Drupal . quickedit . EntityModel > } entities
* @ prop { Array . < Drupal . quickedit . FieldModel > } fields
* /
collections : {
// All in-place editable entities (Drupal.quickedit.EntityModel) on the
// page.
entities : null ,
// All in-place editable fields (Drupal.quickedit.FieldModel) on the page.
2017-07-06 06:21:40 +00:00
fields : null ,
2017-05-19 22:12:53 +00:00
} ,
/ * *
* In - place editors will register themselves in this object .
*
* @ namespace
* /
editors : { } ,
/ * *
* Per - field metadata that indicates whether in - place editing is allowed ,
* which in - place editor should be used , etc .
*
* @ namespace
* /
metadata : {
/ * *
* Check if a field exists in storage .
*
* @ param { string } fieldID
* The field id to check .
*
* @ return { bool }
* Whether it was found or not .
* /
2017-07-06 06:21:40 +00:00
has ( fieldID ) {
2017-05-19 22:12:53 +00:00
return storage . getItem ( this . _prefixFieldID ( fieldID ) ) !== null ;
} ,
/ * *
* Add metadata to a field id .
*
* @ param { string } fieldID
* The field ID to add data to .
* @ param { object } metadata
* Metadata to add .
* /
2017-07-06 06:21:40 +00:00
add ( fieldID , metadata ) {
2017-05-19 22:12:53 +00:00
storage . setItem ( this . _prefixFieldID ( fieldID ) , JSON . stringify ( metadata ) ) ;
} ,
/ * *
* Get a key from a field id .
*
* @ param { string } fieldID
* The field ID to check .
* @ param { string } [ key ]
* The key to check . If empty , will return all metadata .
*
* @ return { object | * }
* The value for the key , if defined . Otherwise will return all metadata
* for the specified field id .
*
* /
2017-07-06 06:21:40 +00:00
get ( fieldID , key ) {
const metadata = JSON . parse ( storage . getItem ( this . _prefixFieldID ( fieldID ) ) ) ;
2017-05-19 22:12:53 +00:00
return ( typeof key === 'undefined' ) ? metadata : metadata [ key ] ;
} ,
/ * *
* Prefix the field id .
*
* @ param { string } fieldID
* The field id to prefix .
*
* @ return { string }
* A prefixed field id .
* /
2017-07-06 06:21:40 +00:00
_prefixFieldID ( fieldID ) {
return ` Drupal.quickedit.metadata. ${ fieldID } ` ;
2017-05-19 22:12:53 +00:00
} ,
/ * *
* Unprefix the field id .
*
* @ param { string } fieldID
* The field id to unprefix .
*
* @ return { string }
* An unprefixed field id .
* /
2017-07-06 06:21:40 +00:00
_unprefixFieldID ( fieldID ) {
2017-05-19 22:12:53 +00:00
// Strip "Drupal.quickedit.metadata.", which is 26 characters long.
return fieldID . substring ( 26 ) ;
} ,
/ * *
* Intersection calculation .
*
* @ param { Array } fieldIDs
* An array of field ids to compare to prefix field id .
*
* @ return { Array }
* The intersection found .
* /
2017-07-06 06:21:40 +00:00
intersection ( fieldIDs ) {
const prefixedFieldIDs = _ . map ( fieldIDs , this . _prefixFieldID ) ;
const intersection = _ . intersection ( prefixedFieldIDs , _ . keys ( sessionStorage ) ) ;
2017-05-19 22:12:53 +00:00
return _ . map ( intersection , this . _unprefixFieldID ) ;
2017-07-06 06:21:40 +00:00
} ,
} ,
2017-05-19 22:12:53 +00:00
} ;
// Clear the Quick Edit metadata cache whenever the current user's set of
// permissions changes.
2017-07-06 06:21:40 +00:00
const permissionsHashKey = Drupal . quickedit . metadata . _prefixFieldID ( 'permissionsHash' ) ;
const permissionsHashValue = storage . getItem ( permissionsHashKey ) ;
const permissionsHash = drupalSettings . user . permissionsHash ;
2017-05-19 22:12:53 +00:00
if ( permissionsHashValue !== permissionsHash ) {
if ( typeof permissionsHash === 'string' ) {
2017-07-06 06:21:40 +00:00
_ . chain ( storage ) . keys ( ) . each ( ( key ) => {
2017-05-19 22:12:53 +00:00
if ( key . substring ( 0 , 26 ) === 'Drupal.quickedit.metadata.' ) {
storage . removeItem ( key ) ;
}
} ) ;
}
storage . setItem ( permissionsHashKey , permissionsHash ) ;
}
/ * *
* Detect contextual links on entities annotated by quickedit .
*
* Queue contextual links to be processed .
*
* @ param { jQuery . Event } event
* The ` drupalContextualLinkAdded ` event .
* @ param { object } data
* An object containing the data relevant to the event .
*
* @ listens event : drupalContextualLinkAdded
* /
2017-07-06 06:21:40 +00:00
$ ( document ) . on ( 'drupalContextualLinkAdded' , ( event , data ) => {
2017-05-19 22:12:53 +00:00
if ( data . $region . is ( '[data-quickedit-entity-id]' ) ) {
// If the contextual link is cached on the client side, an entity instance
// will not yet have been assigned. So assign one.
if ( ! data . $region . is ( '[data-quickedit-entity-instance-id]' ) ) {
data . $region . once ( 'quickedit' ) ;
processEntity ( data . $region . get ( 0 ) ) ;
}
2017-07-06 06:21:40 +00:00
const contextualLink = {
2017-05-19 22:12:53 +00:00
entityID : data . $region . attr ( 'data-quickedit-entity-id' ) ,
entityInstanceID : data . $region . attr ( 'data-quickedit-entity-instance-id' ) ,
el : data . $el [ 0 ] ,
2017-07-06 06:21:40 +00:00
region : data . $region [ 0 ] ,
2017-05-19 22:12:53 +00:00
} ;
// Set up contextual links for this, otherwise queue it to be set up
// later.
if ( ! initializeEntityContextualLink ( contextualLink ) ) {
contextualLinksQueue . push ( contextualLink ) ;
}
}
} ) ;
/ * *
* Extracts the entity ID from a field ID .
*
* @ param { string } fieldID
* A field ID : a string of the format
* ` <entity type>/<id>/<field name>/<language>/<view mode> ` .
*
* @ return { string }
* An entity ID : a string of the format ` <entity type>/<id> ` .
* /
function extractEntityID ( fieldID ) {
return fieldID . split ( '/' ) . slice ( 0 , 2 ) . join ( '/' ) ;
}
/ * *
* Initialize the Quick Edit app .
*
* @ param { HTMLElement } bodyElement
* This document ' s body element .
* /
function initQuickEdit ( bodyElement ) {
Drupal . quickedit . collections . entities = new Drupal . quickedit . EntityCollection ( ) ;
Drupal . quickedit . collections . fields = new Drupal . quickedit . FieldCollection ( ) ;
// Instantiate AppModel (application state) and AppView, which is the
// controller of the whole in-place editing experience.
Drupal . quickedit . app = new Drupal . quickedit . AppView ( {
el : bodyElement ,
model : new Drupal . quickedit . AppModel ( ) ,
entitiesCollection : Drupal . quickedit . collections . entities ,
2017-07-06 06:21:40 +00:00
fieldsCollection : Drupal . quickedit . collections . fields ,
2017-05-19 22:12:53 +00:00
} ) ;
}
/ * *
* Assigns the entity an instance ID .
*
* @ param { HTMLElement } entityElement
* A Drupal Entity API entity ' s DOM element with a data - quickedit - entity - id
* attribute .
* /
function processEntity ( entityElement ) {
2017-07-06 06:21:40 +00:00
const entityID = entityElement . getAttribute ( 'data-quickedit-entity-id' ) ;
2017-05-19 22:12:53 +00:00
if ( ! entityInstancesTracker . hasOwnProperty ( entityID ) ) {
entityInstancesTracker [ entityID ] = 0 ;
}
else {
entityInstancesTracker [ entityID ] ++ ;
}
// Set the calculated entity instance ID for this element.
2017-07-06 06:21:40 +00:00
const entityInstanceID = entityInstancesTracker [ entityID ] ;
2017-05-19 22:12:53 +00:00
entityElement . setAttribute ( 'data-quickedit-entity-instance-id' , entityInstanceID ) ;
}
/ * *
* Fetch the field ' s metadata ; queue or initialize it ( if EntityModel exists ) .
*
* @ param { HTMLElement } fieldElement
* A Drupal Field API field ' s DOM element with a data - quickedit - field - id
* attribute .
* /
function processField ( fieldElement ) {
2017-07-06 06:21:40 +00:00
const metadata = Drupal . quickedit . metadata ;
const fieldID = fieldElement . getAttribute ( 'data-quickedit-field-id' ) ;
const entityID = extractEntityID ( fieldID ) ;
2017-05-19 22:12:53 +00:00
// Figure out the instance ID by looking at the ancestor
// [data-quickedit-entity-id] element's data-quickedit-entity-instance-id
// attribute.
2017-07-06 06:21:40 +00:00
const entityElementSelector = ` [data-quickedit-entity-id=" ${ entityID } "] ` ;
2017-08-07 08:27:31 +00:00
const $entityElement = $ ( entityElementSelector ) ;
// If there are no elements returned from `entityElementSelector`
// throw an error. Check the browser console for this message.
if ( ! $entityElement . length ) {
2017-10-25 17:08:30 +00:00
throw new Error ( ` Quick Edit could not associate the rendered entity field markup (with [data-quickedit-field-id=" ${ fieldID } "]) with the corresponding rendered entity markup: no parent DOM node found with [data-quickedit-entity-id=" ${ entityID } "]. This is typically caused by the theme's template for this entity type forgetting to print the attributes. ` ) ;
2017-08-07 08:27:31 +00:00
}
let entityElement = $ ( fieldElement ) . closest ( $entityElement ) ;
2017-05-19 22:12:53 +00:00
// In the case of a full entity view page, the entity title is rendered
// outside of "the entity DOM node": it's rendered as the page title. So in
// this case, we find the lowest common parent element (deepest in the tree)
// and consider that the entity element.
if ( entityElement . length === 0 ) {
2017-08-07 08:27:31 +00:00
const $lowestCommonParent = $entityElement . parents ( ) . has ( fieldElement ) . first ( ) ;
entityElement = $lowestCommonParent . find ( $entityElement ) ;
2017-05-19 22:12:53 +00:00
}
2017-07-06 06:21:40 +00:00
const entityInstanceID = entityElement
2017-05-19 22:12:53 +00:00
. get ( 0 )
. getAttribute ( 'data-quickedit-entity-instance-id' ) ;
// Early-return if metadata for this field is missing.
if ( ! metadata . has ( fieldID ) ) {
fieldsMetadataQueue . push ( {
el : fieldElement ,
2017-07-06 06:21:40 +00:00
fieldID ,
entityID ,
entityInstanceID ,
2017-05-19 22:12:53 +00:00
} ) ;
return ;
}
// Early-return if the user is not allowed to in-place edit this field.
if ( metadata . get ( fieldID , 'access' ) !== true ) {
return ;
}
// If an EntityModel for this field already exists (and hence also a "Quick
// edit" contextual link), then initialize it immediately.
2017-07-06 06:21:40 +00:00
if ( Drupal . quickedit . collections . entities . findWhere ( { entityID , entityInstanceID } ) ) {
2017-05-19 22:12:53 +00:00
initializeField ( fieldElement , fieldID , entityID , entityInstanceID ) ;
}
// Otherwise: queue the field. It is now available to be set up when its
// corresponding entity becomes in-place editable.
else {
2017-07-06 06:21:40 +00:00
fieldsAvailableQueue . push ( { el : fieldElement , fieldID , entityID , entityInstanceID } ) ;
2017-05-19 22:12:53 +00:00
}
}
/ * *
* Initialize a field ; create FieldModel .
*
* @ param { HTMLElement } fieldElement
* The field ' s DOM element .
* @ param { string } fieldID
* The field ' s ID .
* @ param { string } entityID
* The field 's entity' s ID .
* @ param { string } entityInstanceID
* The field 's entity' s instance ID .
* /
function initializeField ( fieldElement , fieldID , entityID , entityInstanceID ) {
2017-07-06 06:21:40 +00:00
const entity = Drupal . quickedit . collections . entities . findWhere ( {
entityID ,
entityInstanceID ,
2017-05-19 22:12:53 +00:00
} ) ;
$ ( fieldElement ) . addClass ( 'quickedit-field' ) ;
// The FieldModel stores the state of an in-place editable entity field.
2017-07-06 06:21:40 +00:00
const field = new Drupal . quickedit . FieldModel ( {
2017-05-19 22:12:53 +00:00
el : fieldElement ,
2017-07-06 06:21:40 +00:00
fieldID ,
id : ` ${ fieldID } [ ${ entity . get ( 'entityInstanceID' ) } ] ` ,
entity ,
2017-05-19 22:12:53 +00:00
metadata : Drupal . quickedit . metadata . get ( fieldID ) ,
2017-07-06 06:21:40 +00:00
acceptStateChange : _ . bind ( Drupal . quickedit . app . acceptEditorStateChange , Drupal . quickedit . app ) ,
2017-05-19 22:12:53 +00:00
} ) ;
// Track all fields on the page.
Drupal . quickedit . collections . fields . add ( field ) ;
}
/ * *
* Fetches metadata for fields whose metadata is missing .
*
* Fields whose metadata is missing are tracked at fieldsMetadataQueue .
*
* @ param { function } callback
* A callback function that receives field elements whose metadata will just
* have been fetched .
* /
function fetchMissingMetadata ( callback ) {
if ( fieldsMetadataQueue . length ) {
2017-07-06 06:21:40 +00:00
const fieldIDs = _ . pluck ( fieldsMetadataQueue , 'fieldID' ) ;
const fieldElementsWithoutMetadata = _ . pluck ( fieldsMetadataQueue , 'el' ) ;
let entityIDs = _ . uniq ( _ . pluck ( fieldsMetadataQueue , 'entityID' ) , true ) ;
2017-05-19 22:12:53 +00:00
// Ensure we only request entityIDs for which we don't have metadata yet.
entityIDs = _ . difference ( entityIDs , Drupal . quickedit . metadata . intersection ( entityIDs ) ) ;
fieldsMetadataQueue = [ ] ;
$ . ajax ( {
url : Drupal . url ( 'quickedit/metadata' ) ,
type : 'POST' ,
data : {
'fields[]' : fieldIDs ,
2017-07-06 06:21:40 +00:00
'entities[]' : entityIDs ,
2017-05-19 22:12:53 +00:00
} ,
dataType : 'json' ,
2017-07-06 06:21:40 +00:00
success ( results ) {
2017-05-19 22:12:53 +00:00
// Store the metadata.
2017-07-06 06:21:40 +00:00
_ . each ( results , ( fieldMetadata , fieldID ) => {
2017-05-19 22:12:53 +00:00
Drupal . quickedit . metadata . add ( fieldID , fieldMetadata ) ;
} ) ;
callback ( fieldElementsWithoutMetadata ) ;
2017-07-06 06:21:40 +00:00
} ,
2017-05-19 22:12:53 +00:00
} ) ;
}
}
/ * *
* Loads missing in - place editor ' s attachments ( JavaScript and CSS files ) .
*
* Missing in - place editors are those whose fields are actively being used on
* the page but don ' t have .
*
* @ param { function } callback
* Callback function to be called when the missing in - place editors ( if any )
* have been inserted into the DOM . i . e . they may still be loading .
* /
function loadMissingEditors ( callback ) {
2017-07-06 06:21:40 +00:00
const loadedEditors = _ . keys ( Drupal . quickedit . editors ) ;
let missingEditors = [ ] ;
Drupal . quickedit . collections . fields . each ( ( fieldModel ) => {
const metadata = Drupal . quickedit . metadata . get ( fieldModel . get ( 'fieldID' ) ) ;
2017-05-19 22:12:53 +00:00
if ( metadata . access && _ . indexOf ( loadedEditors , metadata . editor ) === - 1 ) {
missingEditors . push ( metadata . editor ) ;
// Set a stub, to prevent subsequent calls to loadMissingEditors() from
// loading the same in-place editor again. Loading an in-place editor
// requires talking to a server, to download its JavaScript, then
// executing its JavaScript, and only then its Drupal.quickedit.editors
// entry will be set.
Drupal . quickedit . editors [ metadata . editor ] = false ;
}
} ) ;
missingEditors = _ . uniq ( missingEditors ) ;
if ( missingEditors . length === 0 ) {
callback ( ) ;
return ;
}
// @see https://www.drupal.org/node/2029999.
// Create a Drupal.Ajax instance to load the form.
2017-07-06 06:21:40 +00:00
const loadEditorsAjax = Drupal . ajax ( {
2017-05-19 22:12:53 +00:00
url : Drupal . url ( 'quickedit/attachments' ) ,
2017-07-06 06:21:40 +00:00
submit : { 'editors[]' : missingEditors } ,
2017-05-19 22:12:53 +00:00
} ) ;
// Implement a scoped insert AJAX command: calls the callback after all AJAX
// command functions have been executed (hence the deferred calling).
2017-07-06 06:21:40 +00:00
const realInsert = Drupal . AjaxCommands . prototype . insert ;
2017-05-19 22:12:53 +00:00
loadEditorsAjax . commands . insert = function ( ajax , response , status ) {
_ . defer ( callback ) ;
realInsert ( ajax , response , status ) ;
} ;
// Trigger the AJAX request, which will should return AJAX commands to
// insert any missing attachments.
loadEditorsAjax . execute ( ) ;
}
/ * *
* Attempts to set up a "Quick edit" link and corresponding EntityModel .
*
* @ param { object } contextualLink
* An object with the following properties :
* - String entityID : a Quick Edit entity identifier , e . g . "node/1" or
* "block_content/5" .
* - String entityInstanceID : a Quick Edit entity instance identifier ,
* e . g . 0 , 1 or n ( depending on whether it ' s the first , second , or n + 1 st
* instance of this entity ) .
* - DOM el : element pointing to the contextual links placeholder for this
* entity .
* - DOM region : element pointing to the contextual region of this entity .
*
* @ return { bool }
* Returns true when a contextual the given contextual link metadata can be
* removed from the queue ( either because the contextual link has been set
* up or because it is certain that in - place editing is not allowed for any
* of its fields ) . Returns false otherwise .
* /
function initializeEntityContextualLink ( contextualLink ) {
2017-07-06 06:21:40 +00:00
const metadata = Drupal . quickedit . metadata ;
2017-05-19 22:12:53 +00:00
// Check if the user has permission to edit at least one of them.
function hasFieldWithPermission ( fieldIDs ) {
2017-07-06 06:21:40 +00:00
for ( let i = 0 ; i < fieldIDs . length ; i ++ ) {
const fieldID = fieldIDs [ i ] ;
2017-05-19 22:12:53 +00:00
if ( metadata . get ( fieldID , 'access' ) === true ) {
return true ;
}
}
return false ;
}
// Checks if the metadata for all given field IDs exists.
function allMetadataExists ( fieldIDs ) {
return fieldIDs . length === metadata . intersection ( fieldIDs ) . length ;
}
// Find all fields for this entity instance and collect their field IDs.
2017-07-06 06:21:40 +00:00
const fields = _ . where ( fieldsAvailableQueue , {
2017-05-19 22:12:53 +00:00
entityID : contextualLink . entityID ,
2017-07-06 06:21:40 +00:00
entityInstanceID : contextualLink . entityInstanceID ,
2017-05-19 22:12:53 +00:00
} ) ;
2017-07-06 06:21:40 +00:00
const fieldIDs = _ . pluck ( fields , 'fieldID' ) ;
2017-05-19 22:12:53 +00:00
// No fields found yet.
if ( fieldIDs . length === 0 ) {
return false ;
}
// The entity for the given contextual link contains at least one field that
// the current user may edit in-place; instantiate EntityModel,
// EntityDecorationView and ContextualLinkView.
else if ( hasFieldWithPermission ( fieldIDs ) ) {
2017-07-06 06:21:40 +00:00
const entityModel = new Drupal . quickedit . EntityModel ( {
2017-05-19 22:12:53 +00:00
el : contextualLink . region ,
entityID : contextualLink . entityID ,
entityInstanceID : contextualLink . entityInstanceID ,
2017-07-06 06:21:40 +00:00
id : ` ${ contextualLink . entityID } [ ${ contextualLink . entityInstanceID } ] ` ,
label : Drupal . quickedit . metadata . get ( contextualLink . entityID , 'label' ) ,
2017-05-19 22:12:53 +00:00
} ) ;
Drupal . quickedit . collections . entities . add ( entityModel ) ;
// Create an EntityDecorationView associated with the root DOM node of the
// entity.
2017-07-06 06:21:40 +00:00
const entityDecorationView = new Drupal . quickedit . EntityDecorationView ( {
2017-05-19 22:12:53 +00:00
el : contextualLink . region ,
2017-07-06 06:21:40 +00:00
model : entityModel ,
2017-05-19 22:12:53 +00:00
} ) ;
entityModel . set ( 'entityDecorationView' , entityDecorationView ) ;
// Initialize all queued fields within this entity (creates FieldModels).
2017-07-06 06:21:40 +00:00
_ . each ( fields , ( field ) => {
2017-05-19 22:12:53 +00:00
initializeField ( field . el , field . fieldID , contextualLink . entityID , contextualLink . entityInstanceID ) ;
} ) ;
fieldsAvailableQueue = _ . difference ( fieldsAvailableQueue , fields ) ;
// Initialization should only be called once. Use Underscore's once method
// to get a one-time use version of the function.
2017-07-06 06:21:40 +00:00
const initContextualLink = _ . once ( ( ) => {
const $links = $ ( contextualLink . el ) . find ( '.contextual-links' ) ;
const contextualLinkView = new Drupal . quickedit . ContextualLinkView ( $ . extend ( {
2017-05-19 22:12:53 +00:00
el : $ ( '<li class="quickedit"><a href="" role="button" aria-pressed="false"></a></li>' ) . prependTo ( $links ) ,
model : entityModel ,
2017-07-06 06:21:40 +00:00
appModel : Drupal . quickedit . app . model ,
2017-05-19 22:12:53 +00:00
} , options ) ) ;
entityModel . set ( 'contextualLinkView' , contextualLinkView ) ;
} ) ;
// Set up ContextualLinkView after loading any missing in-place editors.
loadMissingEditors ( initContextualLink ) ;
return true ;
}
// There was not at least one field that the current user may edit in-place,
// even though the metadata for all fields within this entity is available.
else if ( allMetadataExists ( fieldIDs ) ) {
return true ;
}
return false ;
}
/ * *
* Delete models and queue items that are contained within a given context .
*
* Deletes any contained EntityModels ( plus their associated FieldModels and
* ContextualLinkView ) and FieldModels , as well as the corresponding queues .
*
* After EntityModels , FieldModels must also be deleted , because it is
* possible in Drupal for a field DOM element to exist outside of the entity
* DOM element , e . g . when viewing the full node , the title of the node is not
* rendered within the node ( the entity ) but as the page title .
*
* Note : this will not delete an entity that is actively being in - place
* edited .
*
* @ param { jQuery } $context
* The context within which to delete .
* /
function deleteContainedModelsAndQueues ( $context ) {
2017-07-06 06:21:40 +00:00
$context . find ( '[data-quickedit-entity-id]' ) . addBack ( '[data-quickedit-entity-id]' ) . each ( ( index , entityElement ) => {
2017-05-19 22:12:53 +00:00
// Delete entity model.
2017-07-06 06:21:40 +00:00
const entityModel = Drupal . quickedit . collections . entities . findWhere ( { el : entityElement } ) ;
2017-05-19 22:12:53 +00:00
if ( entityModel ) {
2017-07-06 06:21:40 +00:00
const contextualLinkView = entityModel . get ( 'contextualLinkView' ) ;
2017-05-19 22:12:53 +00:00
contextualLinkView . undelegateEvents ( ) ;
contextualLinkView . remove ( ) ;
// Remove the EntityDecorationView.
entityModel . get ( 'entityDecorationView' ) . remove ( ) ;
// Destroy the EntityModel; this will also destroy its FieldModels.
entityModel . destroy ( ) ;
}
// Filter queue.
function hasOtherRegion ( contextualLink ) {
return contextualLink . region !== entityElement ;
}
contextualLinksQueue = _ . filter ( contextualLinksQueue , hasOtherRegion ) ;
} ) ;
2017-07-06 06:21:40 +00:00
$context . find ( '[data-quickedit-field-id]' ) . addBack ( '[data-quickedit-field-id]' ) . each ( ( index , fieldElement ) => {
2017-05-19 22:12:53 +00:00
// Delete field models.
Drupal . quickedit . collections . fields . chain ( )
2017-07-06 06:21:40 +00:00
. filter ( fieldModel => fieldModel . get ( 'el' ) === fieldElement )
2017-05-19 22:12:53 +00:00
. invoke ( 'destroy' ) ;
// Filter queues.
function hasOtherFieldElement ( field ) {
return field . el !== fieldElement ;
}
fieldsMetadataQueue = _ . filter ( fieldsMetadataQueue , hasOtherFieldElement ) ;
fieldsAvailableQueue = _ . filter ( fieldsAvailableQueue , hasOtherFieldElement ) ;
} ) ;
}
2017-07-06 06:21:40 +00:00
} ( jQuery , _ , Backbone , Drupal , drupalSettings , window . JSON , window . sessionStorage ) ) ;