From c3b9d56ac1a2cb707b9485d3d22a2175aa1994d9 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 29 Jan 2015 20:04:48 +0000 Subject: [PATCH] Updated StreamField blocks API (markdown) --- StreamField-blocks-API.md | 140 ++++++++++---------------------------- 1 file changed, 36 insertions(+), 104 deletions(-) diff --git a/StreamField-blocks-API.md b/StreamField-blocks-API.md index e903cbc..deebd2a 100644 --- a/StreamField-blocks-API.md +++ b/StreamField-blocks-API.md @@ -84,131 +84,64 @@ Typically this will be used to define snippets of HTML within `` block, and prepare an initializer function that can be used on the empty list each time it is inserted into the page (with some crafty prefix rewriting each time): - -> initializeEmptyShoppingList = generateShoppingListInitializer({'itemCount': 0}); -> ... -> initializeEmptyShoppingList('shopping_lists-child-0'); -> initializeEmptyShoppingList('shopping_lists-child-1'); -> initializeEmptyShoppingList('shopping_lists-child-2'); - -generateShoppingListInitializer is a function for generating initializer functions for specific blocks, so we call it a meta-initializer function. So *that's* what we define in shopping_list.js, right? - -Almost, but not quite. The logic within generateShoppingListInitializer needs to access the `` block that we inserted from html_declaration. This script element has an ID derived from definition_prefix, so we don't know this ID in advance when we're writing shopping_list.js. Instead, we have to write a function that takes the definition_prefix and returns generateShoppingListInitializer. - -In shopping_list.js: - - function ShoppingList(definitionPrefix) { - - /* - Set up all the global stuff generateShoppingListInitializer needs - - specifically, fetch the HTML snippet that will be used when spawning new shopping list items. - */ - var newItemHtml = $('#' + definitionPrefix + '-shoppinglistitem').html(); - - var generateShoppingListInitializer = function(contentParams) { - - /* Set up the stuff initializeShoppingList depends on */ - var itemCount = contentParams['itemCount']; - - var initializeShoppingList = function(htmlPrefix) { - /* Set up the Javascript behaviours for a shopping list with 'itemCount' initial items */ - - /* Attach autocomplete to each existing field */ - for (var i = 0; i < itemCount; i++) { - $('#' + htmlPrefix + '-item-' + i).autocomplete(); - } - - /* Make the 'add new' button work */ - $('#' + htmlPrefix + '-addnew').click(function() { - $('#' + htmlPrefix + '-ul').append(newItemHtml); - /* not shown here: fiddling the identifiers in newItemHtml to assign a new unique ID */ - }); - }; - - return initializeShoppingList; - }; - - return generateShoppingListInitializer; + # shopping_list.js + function doAwesomeAjaxyStuff(prefix) { + $('#' + prefix + '-button).click(function() { alert('hello world!'); }); } -In summary: +However, this isn't sufficient for our shopping list block, because the Javascript code needs to know the definition_prefix used by our `` block, which is assigned on page load and not known in advance. Instead, our js_initializer will be a function call that returns the initializer function: -* `metaInit = ShoppingList("{{ definition_prefix }}")` will give you back a meta-initializer function that can tell you how to initialize any shopping list that we might encounter. -* `init = metaInit({itemCount: 3})` will give you back an initializer function to tell you how to initialize a shopping list of length 3. -* `init('matts-shopping-list')` will initialize the Javascript behaviour for the 3-item shopping list whose HTML elements begin with 'matts-shopping-list'. + # shopping_list_block.py + class ShoppingListBlock(Block): + def js_initializer(self): + return "ShoppingList('%s')" % self.definition_prefix -So, back in Python world, we come back to js_initializer: + # shopping_list.js + function ShoppingList(definitionPrefix) { + var newItemHtml = $('#' + definitionPrefix + '-shoppinglistitem').html(); -### js_declaration(self) + var initShoppingList = function(elementPrefix) { + var itemCountField = $('#' + elementPrefix + '-count'); + var itemCount = parseInt(itemCountField.val(), 10); -Returns a Javascript expression string, or None if this block does not require any Javascript behaviour. This expression will be evaluated ONCE per block definition, regardless of how many occurrences of the block there are on the page. This expression evaluates to a "meta-initializer function". + /* Attach autocomplete to each existing field */ + for (var i = 0; i < itemCount; i++) { + $('#' + htmlPrefix + '-item-' + i).autocomplete(); + } -In other words: this method supplies the Javascript function call that kicks this whole sequence of events off: `'ShoppingList("{{ self.definition_prefix }}")'`. + /* Make the 'add new' button work */ + $('#' + htmlPrefix + '-addnew').click(function() { + $('#' + htmlPrefix + '-ul').append(newItemHtml); + /* not shown here: fiddling the identifiers in newItemHtml to assign a new unique ID */ + itemCount++; + itemCountField.val(itemCount); + }); + }; + return initShoppingList; + } -If the block definition has parameters that affect Javascript behaviour - for example, if the block was declared as: +In this way, any parameters that need to be passed from the Python code to the Javascript can be passed within js_initializer. For example, if the block was declared as: ShoppingListPage.content_panels = [ StreamField('shopping_lists', [ShoppingListBlock(autocomplete=True)]) ] -then these will be passed to the Javascript at this point too: +then this can be passed to the Javascript at this point too: ShoppingList( "{{ self.definition_prefix }}", {'autocomplete': {% if self.block_options.autocomplete %}true{% else %}false{% endif %}} ) -### js_declaration_params(self, value) - -Return the Javascript parameter list (a string consisting of zero or more JS expressions separated by commas) that should be passed to the meta-initializer function for a block whose content is 'value'. In other words: this picks out any characteristics of the value that we know will have a bearing on the initialiser code, and packages them up as something we can pass to the meta-initializer. For our shopping list example, the length of the list is one such characteristic (i.e. different length lists will need different initializer functions). Therefore: if 'value' is a list of length 3 then js_declaration_params should return `"{itemCount: 3}"`. - -> **Q.** Can't we just stick our Javascript initialization code inline after the HTML blocks that need it, and avoid this whole chain of initializer functions? -> **A.** No - this approach breaks down when we have nested lists. We would end up with one `` block embedded in another - which is not a problem in itself as long as we consistently escape the `