From 5e70be7ad34784052e1a4e6e118efa959a3a9b60 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Fri, 9 Jan 2015 11:19:08 +0000 Subject: [PATCH] add block JS includes, and media declaration for StreamWidget --- .../static/wagtailadmin/js/blocks/chooser.js | 9 + .../static/wagtailadmin/js/blocks/list.js | 31 ++++ .../static/wagtailadmin/js/blocks/sequence.js | 174 ++++++++++++++++++ .../static/wagtailadmin/js/blocks/stream.js | 57 ++++++ .../static/wagtailadmin/js/blocks/struct.js | 9 + wagtail/wagtailadmin/widgets.py | 4 + 6 files changed, 284 insertions(+) create mode 100644 wagtail/wagtailadmin/static/wagtailadmin/js/blocks/chooser.js create mode 100644 wagtail/wagtailadmin/static/wagtailadmin/js/blocks/list.js create mode 100644 wagtail/wagtailadmin/static/wagtailadmin/js/blocks/sequence.js create mode 100644 wagtail/wagtailadmin/static/wagtailadmin/js/blocks/stream.js create mode 100644 wagtail/wagtailadmin/static/wagtailadmin/js/blocks/struct.js diff --git a/wagtail/wagtailadmin/static/wagtailadmin/js/blocks/chooser.js b/wagtail/wagtailadmin/static/wagtailadmin/js/blocks/chooser.js new file mode 100644 index 000000000..4e8f5dedd --- /dev/null +++ b/wagtail/wagtailadmin/static/wagtailadmin/js/blocks/chooser.js @@ -0,0 +1,9 @@ +(function($) { + window.Chooser = function(definitionPrefix) { + return function(elementPrefix) { + $('#' + elementPrefix + '-button').click(function() { + alert('hello, I am a chooser for ' + elementPrefix + ', which is of type ' + definitionPrefix); + }); + }; + }; +})(jQuery); diff --git a/wagtail/wagtailadmin/static/wagtailadmin/js/blocks/list.js b/wagtail/wagtailadmin/static/wagtailadmin/js/blocks/list.js new file mode 100644 index 000000000..ba8bcacb8 --- /dev/null +++ b/wagtail/wagtailadmin/static/wagtailadmin/js/blocks/list.js @@ -0,0 +1,31 @@ +(function($) { + window.ListBlock = function(opts) { + /* contents of 'opts': + definitionPrefix (required) + childInitializer (optional) - JS initializer function for each child + */ + var listMemberTemplate = $('#' + opts.definitionPrefix + '-newmember').text(); + + return function(elementPrefix) { + var sequence = Sequence({ + 'prefix': elementPrefix, + 'onInitializeMember': function(sequenceMember) { + /* initialize child block's JS behaviour */ + if (opts.childInitializer) { + opts.childInitializer(sequenceMember.prefix + '-value'); + } + + /* initialise delete button */ + $('#' + sequenceMember.prefix + '-delete').click(function() { + sequenceMember.delete(); + }); + } + }); + + /* initialize 'add' button */ + $('#' + elementPrefix + '-add').click(function() { + sequence.insertMemberAtEnd(listMemberTemplate); + }); + }; + }; +})(jQuery); diff --git a/wagtail/wagtailadmin/static/wagtailadmin/js/blocks/sequence.js b/wagtail/wagtailadmin/static/wagtailadmin/js/blocks/sequence.js new file mode 100644 index 000000000..8d21a752f --- /dev/null +++ b/wagtail/wagtailadmin/static/wagtailadmin/js/blocks/sequence.js @@ -0,0 +1,174 @@ +/* Operations on a sequence of items, common to both ListBlock and StreamBlock. +These assume the presence of a container element named "{prefix}-container" for each list item, and +certain hidden fields such as "{prefix}-deleted" as defined in sequence_member.html, but make no assumptions +about layout or visible controls within the block. +For example, they don't assume the presence of a 'delete' button - it's up to the specific subclass +(list.js / stream.js) to attach this to the SequenceMember.delete method. +*/ +(function($) { + window.SequenceMember = function(sequence, prefix) { + var self = {}; + self.prefix = prefix; + self.container = $('#' + self.prefix + '-container'); + var indexField = $('#' + self.prefix + '-order'); + + self.delete = function() { + sequence.deleteMember(self); + }; + self.prependMember = function(template) { + sequence.insertMemberBefore(self, template); + }; + self.appendMember = function(template) { + sequence.insertMemberAfter(self, template); + }; + self._markDeleted = function() { + /* set this list member's hidden 'deleted' flag to true */ + $('#' + self.prefix + '-deleted').val('1'); + /* hide the list item */ + self.container.fadeOut(); + }; + self._markAdded = function() { + self.container.hide(); + self.container.slideDown(); + }; + self.getIndex = function() { + return parseInt(indexField.val(), 10); + }; + self.setIndex = function(i) { + indexField.val(i); + }; + + return self; + }; + window.Sequence = function(opts) { + var self = {}; + var list = $('#' + opts.prefix + '-list'); + var countField = $('#' + opts.prefix + '-count'); + /* NB countField includes deleted items; for the count of non-deleted items, use members.length */ + var members = []; + + self.getCount = function() { + return parseInt(countField.val(), 10); + }; + + function getNewMemberPrefix() { + /* Update the counter and use it to create a prefix for the new list member */ + var newIndex = self.getCount(); + countField.val(newIndex + 1); + return opts.prefix + '-' + newIndex; + } + function postInsertMember(newMember) { + /* run any supplied initializer functions */ + if (opts.onInitializeMember) { + opts.onInitializeMember(newMember); + } + + newMember._markAdded(); + } + + self.insertMemberBefore = function(otherMember, template) { + newMemberPrefix = getNewMemberPrefix(); + + /* Create the new list member element with the real prefix substituted in */ + var elem = $(template.replace(/__PREFIX__/g, newMemberPrefix)); + otherMember.container.before(elem); + var newMember = SequenceMember(self, newMemberPrefix); + var index = otherMember.getIndex(); + + /* bump up index of otherMember and subsequent members */ + for (var i = index; i < members.length; i++) { + members[i].setIndex(i + 1); + } + members.splice(index, 0, newMember); + newMember.setIndex(index); + + postInsertMember(newMember); + + return newMember; + }; + + self.insertMemberAfter = function(otherMember, template) { + newMemberPrefix = getNewMemberPrefix(); + + /* Create the new list member element with the real prefix substituted in */ + var elem = $(template.replace(/__PREFIX__/g, newMemberPrefix)); + otherMember.container.after(elem); + var newMember = SequenceMember(self, newMemberPrefix); + var index = otherMember.getIndex() + 1; + + /* bump up index of subsequent members */ + for (var i = index; i < members.length; i++) { + members[i].setIndex(i + 1); + } + members.splice(index, 0, newMember); + newMember.setIndex(index); + + postInsertMember(newMember); + + return newMember; + }; + + self.insertMemberAtStart = function(template) { + /* NB we can't just do + self.insertMemberBefore(members[0], template) + because that won't work for initially empty lists + */ + newMemberPrefix = getNewMemberPrefix(); + + /* Create the new list member element with the real prefix substituted in */ + var elem = $(template.replace(/__PREFIX__/g, newMemberPrefix)); + list.prepend(elem); + var newMember = SequenceMember(self, newMemberPrefix); + + /* bump up index of all other members */ + for (var i = 0; i < members.length; i++) { + members[i].setIndex(i + 1); + } + members.unshift(newMember); + newMember.setIndex(0); + + postInsertMember(newMember); + + return newMember; + }; + + self.insertMemberAtEnd = function(template) { + newMemberPrefix = getNewMemberPrefix(); + + /* Create the new list member element with the real prefix substituted in */ + var elem = $(template.replace(/__PREFIX__/g, newMemberPrefix)); + list.append(elem); + var newMember = SequenceMember(self, newMemberPrefix); + + newMember.setIndex(members.length); + members.push(newMember); + + postInsertMember(newMember); + + return newMember; + }; + + self.deleteMember = function(member) { + var index = member.getIndex(); + /* reduce index numbers of subsequent members */ + for (var i = index + 1; i < members.length; i++) { + members[i].setIndex(i - 1); + } + /* remove from the 'members' list */ + members.splice(index, 1); + member._markDeleted(); + }; + + /* initialize initial list members */ + for (var i = 0; i < self.getCount(); i++) { + var memberPrefix = opts.prefix + '-' + i; + var sequenceMember = SequenceMember(self, memberPrefix); + members[i] = sequenceMember; + if (opts.onInitializeMember) { + opts.onInitializeMember(sequenceMember); + } + } + + return self; + }; +})(jQuery); diff --git a/wagtail/wagtailadmin/static/wagtailadmin/js/blocks/stream.js b/wagtail/wagtailadmin/static/wagtailadmin/js/blocks/stream.js new file mode 100644 index 000000000..47649e3bd --- /dev/null +++ b/wagtail/wagtailadmin/static/wagtailadmin/js/blocks/stream.js @@ -0,0 +1,57 @@ +(function($) { + window.StreamBlock = function(opts) { + /* Fetch the HTML template strings to be used when adding a new block of each type. + Also reorganise the opts.childBlocks list into a lookup by name + */ + var listMemberTemplates = {}; + var childBlocksByName = {}; + for (var i = 0; i < opts.childBlocks.length; i++) { + var childBlock = opts.childBlocks[i]; + childBlocksByName[childBlock.name] = childBlock; + var template = $('#' + opts.definitionPrefix + '-newmember-' + childBlock.name).text(); + listMemberTemplates[childBlock.name] = template; + } + + return function(elementPrefix) { + var sequence = Sequence({ + 'prefix': elementPrefix, + 'onInitializeMember': function(sequenceMember) { + /* initialize child block's JS behaviour */ + var blockTypeName = $('#' + sequenceMember.prefix + '-type').val(); + var blockOpts = childBlocksByName[blockTypeName]; + if (blockOpts.initializer) { + /* the child block's own elements have the prefix '{list member prefix}-value' */ + blockOpts.initializer(sequenceMember.prefix + '-value'); + } + + /* initialize delete button */ + $('#' + sequenceMember.prefix + '-delete').click(function() { + sequenceMember.delete(); + }); + + /* initialize 'prepend new block' buttons */ + function initializeAppendButton(childBlock) { + var template = listMemberTemplates[childBlock.name]; + $('#' + sequenceMember.prefix + '-add-' + childBlock.name).click(function() { + sequenceMember.appendMember(template); + }); + } + for (var i = 0; i < opts.childBlocks.length; i++) { + initializeAppendButton(opts.childBlocks[i]); + } + } + }); + + /* initialize header menu */ + function initializePrependButton(childBlock) { + var template = listMemberTemplates[childBlock.name]; + $('#' + elementPrefix + '-before-add-' + childBlock.name).click(function() { + sequence.insertMemberAtStart(template); + }); + } + for (var i = 0; i < opts.childBlocks.length; i++) { + initializePrependButton(opts.childBlocks[i]); + } + }; + }; +})(jQuery); diff --git a/wagtail/wagtailadmin/static/wagtailadmin/js/blocks/struct.js b/wagtail/wagtailadmin/static/wagtailadmin/js/blocks/struct.js new file mode 100644 index 000000000..3b78b3ab0 --- /dev/null +++ b/wagtail/wagtailadmin/static/wagtailadmin/js/blocks/struct.js @@ -0,0 +1,9 @@ +window.StructBlock = function(childInitializersByName) { + return function(prefix) { + for (var childName in childInitializersByName) { + var childInitializer = childInitializersByName[childName]; + var childPrefix = prefix + '-' + childName; + childInitializer(childPrefix); + } + }; +}; diff --git a/wagtail/wagtailadmin/widgets.py b/wagtail/wagtailadmin/widgets.py index 7851352a8..8f39a7d7a 100644 --- a/wagtail/wagtailadmin/widgets.py +++ b/wagtail/wagtailadmin/widgets.py @@ -65,5 +65,9 @@ class StreamWidget(widgets.Widget): bound_block = self.block_def.bind(json.loads(value), prefix=name) return bound_block.render_form() + @property + def media(self): + return self.block_def.all_media() + def value_from_datadict(self, data, files, name): return 'lol idk'