An example state after making the appropriate get API request to /bulk_deliveries
{
"deliveryLineItemIds": [
"39773",
"41046",
"41201",
"41706",
"55768",
"55770"
],
"deliveryLineItems": {
"39773": {
"id": null,
"alt_episode_number": 1,
"air_date": "",
"delivery_date": "",
"episode_title": "Time to Expand",
"deliverable_id": 39773,
"deliverable_type": "MediaObject",
"delivery_destination_id": null
},
"41046": {
"id": null,
"alt_episode_number": 2,
"air_date": "",
"delivery_date": "",
"episode_title": "Job Interview",
"deliverable_id": 41046,
"deliverable_type": "MediaObject",
"delivery_destination_id": null
},
"41201": {
"id": null,
"alt_episode_number": 2,
"air_date": "",
"delivery_date": "",
"episode_title": "The Big Secret",
"deliverable_id": 41201,
"deliverable_type": "MediaObject",
"delivery_destination_id": null
},
"41706": {
"id": null,
"alt_episode_number": 1,
"air_date": "",
"delivery_date": "",
"episode_title": "Birthday Party",
"deliverable_id": 41706,
"deliverable_type": "MediaObject",
"delivery_destination_id": null
},
"55768": {
"id": null,
"alt_episode_number": null,
"air_date": "",
"delivery_date": "",
"episode_title": "The Trailer!",
"deliverable_id": 55768,
"deliverable_type": "MediaObject",
"delivery_destination_id": null
},
"55770": {
"id": null,
"alt_episode_number": 1,
"air_date": "",
"delivery_date": "",
"episode_title": "Style Test",
"deliverable_id": 55770,
"deliverable_type": "MediaObject",
"delivery_destination_id": null
}
},
"deliveryDestinations": [],
"longFormDeliveryItemIds": [],
"shortFormDeliveryItemIds": [],
"bulkDeliveryCollection": {
"tiny_stylist-season_1-short_form": {
"collectionName": "season_1-short_form",
"title": "Season 1-short_form",
"format": "Season 1",
"enabled": true,
"deliveryLineItemIds": [
"41046",
"41706"
]
},
"tiny_stylist-season_1-long_form": {
"collectionName": "season_1-long_form",
"title": "Season 1-long_form",
"format": "Season 1",
"enabled": true,
"deliveryLineItemIds": [
"55770"
]
},
"olivia_has_2_moms-season_1-short_form": {
"collectionName": "season_1-short_form",
"title": "Season 1-short_form",
"format": "Season 1",
"enabled": true,
"deliveryLineItemIds": [
"39773",
"41201"
]
},
"olivia_has_2_moms-ancillary": {
"collectionName": "ancillary",
"title": "Ancillary",
"format": null,
"enabled": true,
"deliveryLineItemIds": [
"55768"
]
}
},
"bulkDeliveryShows": {
"tiny_stylist": {
"showName": "tiny_stylist",
"title": "Tiny stylist",
"enabled": true,
"collections": [
"tiny_stylist-season_1-short_form",
"tiny_stylist-season_1-long_form"
]
},
"olivia_has_2_moms": {
"showName": "olivia_has_2_moms",
"title": "Olivia has_2_moms",
"enabled": true,
"collections": [
"olivia_has_2_moms-season_1-short_form",
"olivia_has_2_moms-ancillary"
]
}
}
}
All deliveryLineItems
unique id is their deliverable_id
,
which how we can look up specific deliveryLineItems
, like deliveryLineItems[2]
.
NOTE: This is different from how this reducer is used on a detail page:
(eg. show detail page where deliveryLineItems
are seasons,
or episode details page where ids reflect uncreated
deliveryLineItems
with the prefix new
or saved ones which reference the
deliveryLineItems
activemodel record id).
We will be using the deliveryLineItemIds
array to determine which deliveryLineItem
objects we will be sending back to the
server in the post request back to /bulk_deliveries
as the param ['delivery_line_items'].
An example of this if our delliveryLineItems
object contains
{
1: {
...id,
deliverable_type: null,
alt_episode_number,
etc.
},
2: {
...id,
etc,
},
3: {
...id,
etc.
},
4: {
...id,
etc.
}
5: {
...id,
etc.
}
}
and our deliveryLineItemIds
is only
[1, 2, 5]
then when we make the request to /bulk_deliveries
, the parameters will only be
[
{ id: null, deliverable_id: 1 ... etc. },
{ id: null, deliverable_id: 2, ... etc. },
{ id: null, deliverable_id: 5, ...etc. }
]
Chief in concern here how we will trigger and arrange content around the bulk deliveries container to render a row component imagine a pseudo-code-jsx structure like this:
// This is for illustrative purposes only, not intended to be actual markup
<Show key={show.showName}>
<heading> {show.title} </heading>
<checkEnabled>{show.enabled}</checkEnabled>
{show.collections.map(collection) =>
(<Collection key={collection.collectionName}>
<heading>{collection.title} - {collection.format} </heading>
<checkEnabled>{collection.enabled}</checkEnabled>
{collection.deliveryLineItemIds.map(
(id) => <DeliveryLineItemRow id={id}>
<checkEnabled>{deliveryLineItemIds.includes(id)}</checkEnabled>
</DeliveryLineItemRow>)
}
</Collection>)
}
</Show>
In order to create this structure the components
will iterate over the bulkDeliveryShows
Object keys.
like stated earlier when we want to untoggle
deliveryLineItems
from being exported we will
dropping them out of the deliveryLineItemIds
array.
for example here is how we get all the shows that will render in a connector.
let { bulkDeliveryShows } = getState().deliveryLineItemState.bulkDeliveryShows
Object.keys(bulkDeliveryShows).map(showKey => bulkDeliveryShows[showKey]);
further more if we want to load all collections (seasons, both short and long and ancillary) for that specific show, we could do:
//assuming showKey represents something like "olivia_has_2_moms"
let { bulkDeliveryShows, bulkDeliveryCollections } = getState().deliveryLineItemState;
bulkDeliveryShows[showKey].collections.map(
collectionKey => bulkDeliveryCollections[collectionKey]
)
and further more the same pattern applies to get deliveryLineItems
off a collection
// assuming collectionName is something like "olivia_has_2_moms-season_1-short_form"
let { bulkDeliveryCollections, deliveryLineItems } = getState().deliveryLineItemState;
bulkDeliveryCollections[collectionName].deliveryLineItemIds.map(
deliveryLineItemId => deliveryLineItems[deliveryLineItemId]
)
when we toggle off an entire season we can easily pass it the collectionName
and then do the recomposing on the associated relationships
let activeCollection = bulkDeliveryCollections[`olivia_has_2_moms-season_1-short_form`];
if (activeCollection.enabled) {
let nextDeliveryLineItemSelection = deliveryLineItemIds.filter(
// removes all ids from the deliveryLineItemIds that are part of that collection.
(deliveryId) => !activeCollection.deliveryLineItemIds.includes(deliveryId)
)
} else {
// add the two arrays together
let nextDeliveryLineItemSelection = deliveryLineItemIds.concat(
activeCollection.deliveryLineItemIds
).filter( // this is done to insure uniqueness of ids.
(id, index, arr) => arr.indexOf(id) === index)
);
}
// then we could recompose this object with the state at it's key `olivia_has_2_moms-season_1-short_form`
// with the enabled flag in the opposite direction
// in state like:
return {
...currentState,
bulkDeliveryCollections: {
...currentState.bulkDeliveryCollections, // include all existing collections
[`olivia_has_2_moms-season_1-short_form`]: {
...activeCollection, // none of the other properties have changed
enabled: !activeCollection.enabled
}
},
// now this has either removed or
// added those ids for the rows that are selected.
deliveryLineItemIds: nextDeliveryLineItemSelection
}
The same principle can apply to shows as well, this time we memoize all the deliverable_ids
across all the collections into a single array (idsToFilter
), so we can use that to bulk add or remove deliveryLineItemIds
let nextDeliveryLineItemIdSelection, idsToFilter = [];
let activeShow = currentState.bulkDeliveryShows[value.showName];
let isEnabled = (typeof value.forceDirection === 'boolean' ?
// this makes our forceDirection an optional value from the action,
// we may not need this, but i'm adding the backdoor just in case
value.forceDirection :
activeShow.enabled);
//first we update all collections's effected enabled flag
let nextCollections = activeShow.collections.reduce(
(collections, collectionId) => {
// this pushes the deliveryLineItemIds array
// attached to the collection elements into the idsToFilter array
Array.prototype.push.apply(
null,
idsToFilter, currentState.bulkDeliveryCollections[collectionId].deliveryLineItemIds
)
collections[collectionId] = {
...currentState.bulkDeliveryCollections[collectionId],
enabled: !isEnabled
}
return collections;
}, {})
// now its pretty easy to take all those ids in `idsToFilter`
// and do the same thing we've done with the collections above
if (isEnabled) {
nextDeliveryLineItemIdSelection = currentState.deliveryLineItemIds.filter(
deliveryId => (idsToFilter.indexOf(deliveryId) === -1)
)
} else {
nextDeliveryLineItemIdSelection = currentState.deliveryLineItemIds.concat(
idsToFilter
).filter(
(id, index, combinedArr) => combinedArr.indexOf(id) === index
)
}
return {
...currentState,
bulkDeliveryShows: {
...currentState.bulkDeliveryShows,
[value.showName]: {...activeShow, enabled: !isEnabled}
},
//merge the old collections with the new
bulkDeliveryCollections: Object.assign(
currentState.bulkDeliveryCollections, nextCollections
),
deliveryLineItemIds: nextDeliveryLineItemIdSelection
}
Now for our final trick, we will update all the values of the deliveryLineItems
in one fell swoop.
return {
...currentState,
// iterate over all the deliveryLineItem keys,
// whether they are selected or not in `deliveryLineItemIds`
deliveryLineItems: Object.keys(
currentState.deliveryLineItems
).reduce((updatedDeliveryLineItems, deliveryKey) => {
updatedDeliveryLineItems[deliveryKey] = {
//copy over all existing attributes of the `deliveryLineItem`
...currentState.deliveryLineItems[deliveryKey],
// overwrite the attribute that matches the fieldName
[value.fieldName]: value.fieldValue
}
return updatedDeliveryLineItems
}, {})
}