diff --git a/Gruntfile.js b/Gruntfile.js index c3d1581b..6a0beefa 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -93,7 +93,6 @@ module.exports = function( grunt ) { dist: { files : { 'js/build/shortcode-ui.js' : ['js/src/shortcode-ui.js'], - 'js/build/field-attachment.js' : ['js/src/field-attachment.js'], 'js/build/field-color.js' : ['js/src/field-color.js'], 'js/build/field-post-select.js' : ['js/src/field-post-select.js'], }, diff --git a/inc/fields/class-field-attachment.php b/inc/fields/class-field-attachment.php index e405c381..ed4b3a6a 100644 --- a/inc/fields/class-field-attachment.php +++ b/inc/fields/class-field-attachment.php @@ -37,11 +37,7 @@ public function filter_shortcode_ui_fields( $fields ) { public function action_enqueue_shortcode_ui() { - $script = plugins_url( 'js/build/field-attachment.js', dirname( dirname( __FILE__ ) ) ); - - wp_enqueue_script( 'shortcake-field-attachment', $script, array( 'shortcode-ui' ) ); - - wp_localize_script( 'shortcake-field-attachment', 'ShortcakeImageFieldData', array( + wp_localize_script( 'shortcode-ui', 'ShortcakeImageFieldData', array( 'defaultArgs' => array( 'libraryType' => null, // array of mime types. eg image, image/jpg, application, application/pdf. 'addButton' => __( 'Select Attachment', 'shortcode-ui' ), diff --git a/js-tests/build/specs.js b/js-tests/build/specs.js index d7f8f24c..11a40a67 100644 --- a/js-tests/build/specs.js +++ b/js-tests/build/specs.js @@ -320,7 +320,8 @@ var ShortcodeAttributes = Backbone.Collection.extend({ return new this.constructor(_.map(this.models, function(m) { return m.clone(); })); - } + }, + }); module.exports = ShortcodeAttributes; @@ -477,8 +478,7 @@ Shortcode = Backbone.Model.extend({ return template; - } - + }, }); module.exports = Shortcode; diff --git a/js/build/field-attachment.js b/js/build/field-attachment.js deleted file mode 100644 index 0468bb0b..00000000 --- a/js/build/field-attachment.js +++ /dev/null @@ -1,456 +0,0 @@ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o'); - - if ( 'image' !== attachment.type ) { - - $( '', { - src: attachment.icon, - alt: attachment.title, - } ).appendTo( $thumbnail ); - - $( '
', { - class: 'filename', - html: '
' + attachment.title + '
', - } ).appendTo( $thumbnail ); - - } else { - - attachmentThumb = (typeof attachment.sizes.thumbnail !== 'undefined') ? - attachment.sizes.thumbnail : - _.first( _.sortBy( attachment.sizes, 'width' ) ); - - $( '', { - src: attachmentThumb.url, - width: attachmentThumb.width, - height: attachmentThumb.height, - alt: attachment.alt, - } ) .appendTo( $thumbnail ) - - } - - $thumbnail.find( 'img' ).wrap( '
' ); - $container.append( $thumbnail ); - $container.toggleClass( 'has-attachment', true ); - - } - - /** - * Remove the attachment. - * Render preview & Update the model. - */ - var removeAttachment = function() { - - model.set( 'value', null ); - - $container.toggleClass( 'has-attachment', false ); - $container.toggleClass( 'has-attachment', false ); - $container.find( '.thumbnail' ).remove(); - } - - // Add initial Attachment if available. - updateAttachment( model.get( 'value' ) ); - - // Remove file when the button is clicked. - $removeButton.click( function(e) { - e.preventDefault(); - removeAttachment(); - }); - - // Open media frame when add button is clicked - $addButton.click( function(e) { - e.preventDefault(); - frame.open(); - } ); - - // Update the attachment when an item is selected. - frame.on( 'select', function() { - - var selection = frame.state().get('selection'); - attachment = selection.first(); - - updateAttachment( attachment.id ); - - frame.close(); - - }); - - } - -} ); - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./utils/sui.js":7,"./views/edit-attribute-field.js":8}],4:[function(require,module,exports){ -(function (global){ -var Backbone = (typeof window !== "undefined" ? window.Backbone : typeof global !== "undefined" ? global.Backbone : null); - -/** - * Shortcode Attribute Model. - */ -var InnerContent = Backbone.Model.extend({ - defaults : { - label: shortcodeUIData.strings.insert_content_label, - type: 'textarea', - value: '', - placeholder: '', - }, -}); - -module.exports = InnerContent; - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],5:[function(require,module,exports){ -(function (global){ -var Backbone = (typeof window !== "undefined" ? window.Backbone : typeof global !== "undefined" ? global.Backbone : null); - -var ShortcodeAttribute = Backbone.Model.extend({ - defaults: { - attr: '', - label: '', - type: '', - value: '', - description: '', - meta: { - placeholder: '', - }, - }, -}); - -module.exports = ShortcodeAttribute; - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],6:[function(require,module,exports){ -(function (global){ -var Backbone = (typeof window !== "undefined" ? window.Backbone : typeof global !== "undefined" ? global.Backbone : null); -var ShortcodeAttributes = require('./../collections/shortcode-attributes.js'); -var InnerContent = require('./inner-content.js'); - -Shortcode = Backbone.Model.extend({ - - defaults: { - label: '', - shortcode_tag: '', - attrs: new ShortcodeAttributes, - }, - - /** - * Custom set method. - * Handles setting the attribute collection. - */ - set: function( attributes, options ) { - - if ( attributes.attrs !== undefined && ! ( attributes.attrs instanceof ShortcodeAttributes ) ) { - attributes.attrs = new ShortcodeAttributes( attributes.attrs ); - } - - if ( attributes.inner_content && ! ( attributes.inner_content instanceof InnerContent ) ) { - attributes.inner_content = new InnerContent( attributes.inner_content ); - } - - return Backbone.Model.prototype.set.call(this, attributes, options); - }, - - /** - * Custom toJSON. - * Handles converting the attribute collection to JSON. - */ - toJSON: function( options ) { - options = Backbone.Model.prototype.toJSON.call(this, options); - if ( options.attrs && ( options.attrs instanceof ShortcodeAttributes ) ) { - options.attrs = options.attrs.toJSON(); - } - if ( options.inner_content && ( options.inner_content instanceof InnerContent ) ) { - options.inner_content = options.inner_content.toJSON(); - } - return options; - }, - - /** - * Custom clone - * Make sure we don't clone a reference to attributes. - */ - clone: function() { - var clone = Backbone.Model.prototype.clone.call( this ); - clone.set( 'attrs', clone.get( 'attrs' ).clone() ); - if ( clone.get( 'inner_content' ) ) { - clone.set( 'inner_content', clone.get( 'inner_content' ).clone() ); - } - return clone; - }, - - /** - * Get the shortcode as... a shortcode! - * - * @return string eg [shortcode attr1=value] - */ - formatShortcode: function() { - - var template, shortcodeAttributes, attrs = [], content, self = this; - - this.get( 'attrs' ).each( function( attr ) { - - // Skip empty attributes. - if ( ! attr.get( 'value' ) || attr.get( 'value' ).length < 1 ) { - return; - } - - attrs.push( attr.get( 'attr' ) + '="' + attr.get( 'value' ) + '"' ); - - } ); - - if ( this.get( 'inner_content' ) ) { - content = this.get( 'inner_content' ).get( 'value' ); - } - - if ( attrs.length > 0 ) { - template = "[{{ shortcode }} {{ attributes }}]" - } else { - template = "[{{ shortcode }}]" - } - - if ( content && content.length > 0 ) { - template += "{{ content }}[/{{ shortcode }}]" - } - - template = template.replace( /{{ shortcode }}/g, this.get('shortcode_tag') ); - template = template.replace( /{{ attributes }}/g, attrs.join( ' ' ) ); - template = template.replace( /{{ content }}/g, content ); - - return template; - - } - -}); - -module.exports = Shortcode; - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./../collections/shortcode-attributes.js":1,"./inner-content.js":4}],7:[function(require,module,exports){ -var Shortcodes = require('./../collections/shortcodes.js'); - -window.Shortcode_UI = window.Shortcode_UI || { - shortcodes: new Shortcodes, - views: {}, - controllers: {}, -}; - -module.exports = window.Shortcode_UI; - -},{"./../collections/shortcodes.js":2}],8:[function(require,module,exports){ -(function (global){ -var Backbone = (typeof window !== "undefined" ? window.Backbone : typeof global !== "undefined" ? global.Backbone : null), - sui = require('./../utils/sui.js'), - $ = (typeof window !== "undefined" ? window.jQuery : typeof global !== "undefined" ? global.jQuery : null); - -var editAttributeField = Backbone.View.extend( { - - tagName: "div", - - events: { - 'keyup input[type="text"]': 'updateValue', - 'keyup textarea': 'updateValue', - 'change select': 'updateValue', - 'change input[type=checkbox]': 'updateValue', - 'change input[type=radio]': 'updateValue', - 'change input[type=email]': 'updateValue', - 'change input[type=number]': 'updateValue', - 'change input[type=date]': 'updateValue', - 'change input[type=url]': 'updateValue', - }, - - render: function() { - - var data = jQuery.extend( { - id: 'shortcode-ui-' + this.model.get( 'attr' ) + '-' + this.model.cid, - }, this.model.toJSON() ); - - // Convert meta JSON to attribute string. - var _meta = []; - for ( var key in data.meta ) { - - // Boolean attributes can only require attribute key, not value. - if ( 'boolean' === typeof( data.meta[ key ] ) ) { - - // Only set truthy boolean attributes. - if ( data.meta[ key ] ) { - _meta.push( _.escape( key ) ); - } - - } else { - - _meta.push( _.escape( key ) + '="' + _.escape( data.meta[ key ] ) + '"' ); - - } - - } - - data.meta = _meta.join( ' ' ); - - this.$el.html( this.template( data ) ); - this.updateValue(); - - return this - }, - - /** - * Input Changed Update Callback. - * - * If the input field that has changed is for content or a valid attribute, - * then it should update the model. If a callback function is registered - * for this attribute, it should be called as well. - */ - updateValue: function( e ) { - - if ( this.model.get( 'attr' ) ) { - var $el = $( this.el ).find( '[name=' + this.model.get( 'attr' ) + ']' ); - } else { - var $el = $( this.el ).find( '[name="inner_content"]' ); - } - - if ( 'radio' === this.model.attributes.type ) { - this.model.set( 'value', $el.filter(':checked').first().val() ); - } else if ( 'checkbox' === this.model.attributes.type ) { - this.model.set( 'value', $el.is( ':checked' ) ); - } else { - this.model.set( 'value', $el.val() ); - } - - var shortcodeName = this.shortcode.attributes.shortcode_tag, - attributeName = this.model.get( 'attr' ), - hookName = [ shortcodeName, attributeName ].join( '.' ), - changed = this.model.changed, - collection = _.flatten( _.values( this.views.parent.views._views ) ), - shortcode = this.shortcode; - - /* - * Action run when an attribute value changes on a shortcode - * - * Called as `{shortcodeName}.{attributeName}`. - * - * @param changed (object) - * The update, ie. { "changed": "newValue" } - * @param viewModels (array) - * The collections of views (editAttributeFields) - * which make up this shortcode UI form - * @param shortcode (object) - * Reference to the shortcode model which this attribute belongs to. - */ - wp.shortcake.hooks.doAction( hookName, changed, collection, shortcode ); - - } - -} ); - -sui.views.editAttributeField = editAttributeField; -module.exports = editAttributeField; - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./../utils/sui.js":7}]},{},[3]); diff --git a/js/build/field-color.js b/js/build/field-color.js index 0d5d663c..6352eac4 100644 --- a/js/build/field-color.js +++ b/js/build/field-color.js @@ -13,7 +13,8 @@ var ShortcodeAttributes = Backbone.Collection.extend({ return new this.constructor(_.map(this.models, function(m) { return m.clone(); })); - } + }, + }); module.exports = ShortcodeAttributes; @@ -193,8 +194,7 @@ Shortcode = Backbone.Model.extend({ return template; - } - + }, }); module.exports = Shortcode; @@ -222,15 +222,15 @@ var editAttributeField = Backbone.View.extend( { tagName: "div", events: { - 'keyup input[type="text"]': 'updateValue', - 'keyup textarea': 'updateValue', - 'change select': 'updateValue', - 'change input[type=checkbox]': 'updateValue', - 'change input[type=radio]': 'updateValue', - 'change input[type=email]': 'updateValue', - 'change input[type=number]': 'updateValue', - 'change input[type=date]': 'updateValue', - 'change input[type=url]': 'updateValue', + 'keyup input[type="text"]': 'inputChanged', + 'keyup textarea': 'inputChanged', + 'change select': 'inputChanged', + 'change input[type=checkbox]': 'inputChanged', + 'change input[type=radio]': 'inputChanged', + 'change input[type=email]': 'inputChanged', + 'change input[type=number]': 'inputChanged', + 'change input[type=date]': 'inputChanged', + 'change input[type=url]': 'inputChanged', }, render: function() { @@ -262,7 +262,7 @@ var editAttributeField = Backbone.View.extend( { data.meta = _meta.join( ' ' ); this.$el.html( this.template( data ) ); - this.updateValue(); + this.triggerCallbacks(); return this }, @@ -274,22 +274,35 @@ var editAttributeField = Backbone.View.extend( { * then it should update the model. If a callback function is registered * for this attribute, it should be called as well. */ - updateValue: function( e ) { + inputChanged: function( e ) { - if ( this.model.get( 'attr' ) ) { - var $el = $( this.el ).find( '[name=' + this.model.get( 'attr' ) + ']' ); + if ( this.getValue( 'attr' ) ) { + var $el = this.$el.find( '[name="' + this.model.get( 'attr' ) + '"]' ); } else { - var $el = $( this.el ).find( '[name="inner_content"]' ); + var $el = this.$el.find( '[name="inner_content"]' ); } if ( 'radio' === this.model.attributes.type ) { - this.model.set( 'value', $el.filter(':checked').first().val() ); + this.setValue( $el.filter(':checked').first().val() ); } else if ( 'checkbox' === this.model.attributes.type ) { - this.model.set( 'value', $el.is( ':checked' ) ); + this.setValue( $el.is( ':checked' ) ); } else { - this.model.set( 'value', $el.val() ); + this.setValue( $el.val() ); } + this.triggerCallbacks(); + }, + + getValue: function() { + return this.model.get( 'value' ); + }, + + setValue: function( val ) { + this.model.set( 'value', val ); + }, + + triggerCallbacks: function() { + var shortcodeName = this.shortcode.attributes.shortcode_tag, attributeName = this.model.get( 'attr' ), hookName = [ shortcodeName, attributeName ].join( '.' ), @@ -314,7 +327,25 @@ var editAttributeField = Backbone.View.extend( { } -} ); +}, { + + /** + * Get an attribute field from a shortcode by name. + * + * Usage: `sui.views.editAttributeField.getField( collection, 'title')` + * + * @param array collection of editAttributeFields + * @param string attribute name + * @return editAttributeField The view corresponding to the matching field + */ + getField: function( collection, attr ) { + return _.find( collection, + function( viewModel ) { + return attr === viewModel.model.get('attr'); + } + ); + } +}); sui.views.editAttributeField = editAttributeField; module.exports = editAttributeField; diff --git a/js/build/shortcode-ui.js b/js/build/shortcode-ui.js index 42f9b410..dc6d9015 100644 --- a/js/build/shortcode-ui.js +++ b/js/build/shortcode-ui.js @@ -13,7 +13,8 @@ var ShortcodeAttributes = Backbone.Collection.extend({ return new this.constructor(_.map(this.models, function(m) { return m.clone(); })); - } + }, + }); module.exports = ShortcodeAttributes; @@ -226,8 +227,7 @@ Shortcode = Backbone.Model.extend({ return template; - } - + }, }); module.exports = Shortcode; @@ -264,7 +264,7 @@ $(document).ready(function(){ }); }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./collections/shortcodes.js":2,"./utils/shortcode-view-constructor.js":8,"./utils/sui.js":9,"./views/media-frame.js":14}],8:[function(require,module,exports){ +},{"./collections/shortcodes.js":2,"./utils/shortcode-view-constructor.js":8,"./utils/sui.js":9,"./views/media-frame.js":15}],8:[function(require,module,exports){ (function (global){ var sui = require('./sui.js'), wp = (typeof window !== "undefined" ? window.wp : typeof global !== "undefined" ? global.wp : null), @@ -602,6 +602,194 @@ window.Shortcode_UI = window.Shortcode_UI || { module.exports = window.Shortcode_UI; },{"./../collections/shortcodes.js":2}],10:[function(require,module,exports){ +var sui = require('./../utils/sui.js'); + +var editAttributeFieldAttachment = sui.views.editAttributeField.extend( { + + events: { + 'click .add' : '_openMediaFrame', + 'click .remove' : '_removeAttachment', + 'selectAttachment' : '_selectAttachment', + }, + + /** + * Update the field attachment. + * Re-renders UI. + * If ID is empty - does nothing. + * + * @param {int} id Attachment ID + */ + updateValue: function( id ) { + + if ( ! id ) { + return; + } + + this.setValue( id ); + + var self = this; + + if ( editAttributeFieldAttachment.getFromCache( id ) ) { + self._renderPreview( this._getFromCache( id ) ); + return; + + // Call the updateValue() function, to trigger any listeners + // hooked on it. + self.triggerCallbacks(); + } + + this.$container.addClass( 'loading' ); + + wp.ajax.post( 'get-attachment', { + 'id': id + } ).done( function( attachment ) { + // Cache for later. + editAttributeFieldAttachment.setInCache( id, attachment ); + self._renderPreview( attachment ); + self.$container.removeClass( 'loading' ); + + // Call the updateValue() function, to trigger any listeners + // hooked on it. + self.triggerCallbacks(); + } ); + }, + + render: function() { + + // Set model default values. + for ( var arg in ShortcakeImageFieldData.defaultArgs ) { + if ( ! this.model.get( arg ) ) { + this.model.set( arg, ShortcakeImageFieldData.defaultArgs[ arg ] ); + } + } + + this.$el.html( this.template( this.model.toJSON() ) ); + + this.$container = this.$el.find( '.shortcake-attachment-preview' ); + var $addButton = this.$container.find( 'button.add' ); + + this.frame = wp.media( { + multiple: false, + title: this.model.get( 'frameTitle' ), + library: { + type: this.model.get( 'libraryType' ), + }, + } ); + + // Add initial Attachment if available. + this.updateValue( this.model.get( 'value' ) ); + + }, + + /** + * Renders attachment preview in field. + * @param {object} attachment model + * @return null + */ + _renderPreview: function( attachment ) { + + var $thumbnail = jQuery('
'); + + if ( 'image' !== attachment.type ) { + + jQuery( '', { + src: attachment.icon, + alt: attachment.title, + } ).appendTo( $thumbnail ); + + jQuery( '
', { + class: 'filename', + html: '
' + attachment.title + '
', + } ).appendTo( $thumbnail ); + + } else { + + attachmentThumb = (typeof attachment.sizes.thumbnail !== 'undefined') ? + attachment.sizes.thumbnail : + _.first( _.sortBy( attachment.sizes, 'width' ) ); + + jQuery( '', { + src: attachmentThumb.url, + width: attachmentThumb.width, + height: attachmentThumb.height, + alt: attachment.alt, + } ) .appendTo( $thumbnail ) + + } + + $thumbnail.find( 'img' ).wrap( '
' ); + this.$container.append( $thumbnail ); + this.$container.toggleClass( 'has-attachment', true ); + + }, + + /** + * Open media frame when add button is clicked. + * + */ + _openMediaFrame: function(e) { + e.preventDefault(); + this.frame.open(); + + var self = this; + this.frame.on( 'select', function() { + self.$el.trigger( 'selectAttachment' ); + } ); + + }, + + /** + * When an attachment is selected from the media frame, update the model value. + * + */ + _selectAttachment: function(e) { + var selection = this.frame.state().get('selection'); + attachment = selection.first(); + + this.updateValue( attachment.id ); + this.frame.close(); + }, + + /** + * Remove the attachment. + * Render preview & Update the model. + */ + _removeAttachment: function(e) { + e.preventDefault(); + + this.model.set( 'value', null ); + + this.$container.toggleClass( 'has-attachment', false ); + this.$container.find( '.thumbnail' ).remove(); + }, + +}, { + + _idCache: {}, + + /** + * Store attachments in a cache for quicker loading. + */ + setInCache: function( id, attachment ) { + this._idCache[ id ] = attachment; + }, + + /** + * Retrieve an attachment from the cache. + */ + getFromCache: function( id ){ + if ( 'undefined' === typeof this._idCache[ id ] ) { + return false; + } + return this._idCache[ id ]; + }, + +}); + +module.exports = sui.views.editAttributeFieldAttachment = editAttributeFieldAttachment; + + +},{"./../utils/sui.js":9}],11:[function(require,module,exports){ (function (global){ var Backbone = (typeof window !== "undefined" ? window.Backbone : typeof global !== "undefined" ? global.Backbone : null), sui = require('./../utils/sui.js'), @@ -612,15 +800,15 @@ var editAttributeField = Backbone.View.extend( { tagName: "div", events: { - 'keyup input[type="text"]': 'updateValue', - 'keyup textarea': 'updateValue', - 'change select': 'updateValue', - 'change input[type=checkbox]': 'updateValue', - 'change input[type=radio]': 'updateValue', - 'change input[type=email]': 'updateValue', - 'change input[type=number]': 'updateValue', - 'change input[type=date]': 'updateValue', - 'change input[type=url]': 'updateValue', + 'keyup input[type="text"]': 'inputChanged', + 'keyup textarea': 'inputChanged', + 'change select': 'inputChanged', + 'change input[type=checkbox]': 'inputChanged', + 'change input[type=radio]': 'inputChanged', + 'change input[type=email]': 'inputChanged', + 'change input[type=number]': 'inputChanged', + 'change input[type=date]': 'inputChanged', + 'change input[type=url]': 'inputChanged', }, render: function() { @@ -652,7 +840,7 @@ var editAttributeField = Backbone.View.extend( { data.meta = _meta.join( ' ' ); this.$el.html( this.template( data ) ); - this.updateValue(); + this.triggerCallbacks(); return this }, @@ -664,22 +852,35 @@ var editAttributeField = Backbone.View.extend( { * then it should update the model. If a callback function is registered * for this attribute, it should be called as well. */ - updateValue: function( e ) { + inputChanged: function( e ) { - if ( this.model.get( 'attr' ) ) { - var $el = $( this.el ).find( '[name=' + this.model.get( 'attr' ) + ']' ); + if ( this.getValue( 'attr' ) ) { + var $el = this.$el.find( '[name="' + this.model.get( 'attr' ) + '"]' ); } else { - var $el = $( this.el ).find( '[name="inner_content"]' ); + var $el = this.$el.find( '[name="inner_content"]' ); } if ( 'radio' === this.model.attributes.type ) { - this.model.set( 'value', $el.filter(':checked').first().val() ); + this.setValue( $el.filter(':checked').first().val() ); } else if ( 'checkbox' === this.model.attributes.type ) { - this.model.set( 'value', $el.is( ':checked' ) ); + this.setValue( $el.is( ':checked' ) ); } else { - this.model.set( 'value', $el.val() ); + this.setValue( $el.val() ); } + this.triggerCallbacks(); + }, + + getValue: function() { + return this.model.get( 'value' ); + }, + + setValue: function( val ) { + this.model.set( 'value', val ); + }, + + triggerCallbacks: function() { + var shortcodeName = this.shortcode.attributes.shortcode_tag, attributeName = this.model.get( 'attr' ), hookName = [ shortcodeName, attributeName ].join( '.' ), @@ -704,18 +905,41 @@ var editAttributeField = Backbone.View.extend( { } -} ); +}, { + + /** + * Get an attribute field from a shortcode by name. + * + * Usage: `sui.views.editAttributeField.getField( collection, 'title')` + * + * @param array collection of editAttributeFields + * @param string attribute name + * @return editAttributeField The view corresponding to the matching field + */ + getField: function( collection, attr ) { + return _.find( collection, + function( viewModel ) { + return attr === viewModel.model.get('attr'); + } + ); + } +}); sui.views.editAttributeField = editAttributeField; module.exports = editAttributeField; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./../utils/sui.js":9}],11:[function(require,module,exports){ +},{"./../utils/sui.js":9}],12:[function(require,module,exports){ (function (global){ var wp = (typeof window !== "undefined" ? window.wp : typeof global !== "undefined" ? global.wp : null), -sui = require('./../utils/sui.js'), -backbone = (typeof window !== "undefined" ? window.Backbone : typeof global !== "undefined" ? global.Backbone : null), -editAttributeField = require('./edit-attribute-field.js'); + sui = require('./../utils/sui.js'), + backbone = (typeof window !== "undefined" ? window.Backbone : typeof global !== "undefined" ? global.Backbone : null), + editAttributeField = require('./edit-attribute-field.js'), + + // Additional attribute field types: these fields are all standalone in functionality, + // but bundled here for simplicity to save an HTTP request. + editAttributeFieldAttachment = require('./edit-attribute-field-attachment.js'); + /** * Single edit shortcode content view. @@ -787,7 +1011,7 @@ var EditShortcodeForm = wp.Backbone.View.extend({ module.exports = EditShortcodeForm; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./../utils/sui.js":9,"./edit-attribute-field.js":10}],12:[function(require,module,exports){ +},{"./../utils/sui.js":9,"./edit-attribute-field-attachment.js":10,"./edit-attribute-field.js":11}],13:[function(require,module,exports){ (function (global){ var wp = (typeof window !== "undefined" ? window.wp : typeof global !== "undefined" ? global.wp : null); @@ -821,7 +1045,7 @@ var insertShortcodeListItem = wp.Backbone.View.extend({ module.exports = insertShortcodeListItem; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],13:[function(require,module,exports){ +},{}],14:[function(require,module,exports){ (function (global){ var wp = (typeof window !== "undefined" ? window.wp : typeof global !== "undefined" ? global.wp : null); var Backbone = (typeof window !== "undefined" ? window.Backbone : typeof global !== "undefined" ? global.Backbone : null); @@ -867,7 +1091,7 @@ var insertShortcodeList = wp.Backbone.View.extend({ module.exports = insertShortcodeList; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./../collections/shortcodes.js":2,"./insert-shortcode-list-item.js":12}],14:[function(require,module,exports){ +},{"./../collections/shortcodes.js":2,"./insert-shortcode-list-item.js":13}],15:[function(require,module,exports){ (function (global){ var wp = (typeof window !== "undefined" ? window.wp : typeof global !== "undefined" ? global.wp : null), $ = (typeof window !== "undefined" ? window.jQuery : typeof global !== "undefined" ? global.jQuery : null), @@ -994,7 +1218,7 @@ var mediaFrame = postMediaFrame.extend( { module.exports = mediaFrame; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./../controllers/media-controller.js":3,"./media-toolbar":15,"./shortcode-ui":18}],15:[function(require,module,exports){ +},{"./../controllers/media-controller.js":3,"./media-toolbar":16,"./shortcode-ui":19}],16:[function(require,module,exports){ (function (global){ var wp = (typeof window !== "undefined" ? window.wp : typeof global !== "undefined" ? global.wp : null); @@ -1026,7 +1250,7 @@ var Toolbar = wp.media.view.Toolbar.extend({ module.exports = Toolbar; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],16:[function(require,module,exports){ +},{}],17:[function(require,module,exports){ (function (global){ var wp = (typeof window !== "undefined" ? window.wp : typeof global !== "undefined" ? global.wp : null); sui = require('./../utils/sui.js'); @@ -1073,7 +1297,7 @@ var SearchShortcode = wp.media.view.Search.extend({ sui.views.SearchShortcode = SearchShortcode; module.exports = SearchShortcode; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./../utils/sui.js":9}],17:[function(require,module,exports){ +},{"./../utils/sui.js":9}],18:[function(require,module,exports){ (function (global){ var Backbone = (typeof window !== "undefined" ? window.Backbone : typeof global !== "undefined" ? global.Backbone : null), $ = (typeof window !== "undefined" ? window.jQuery : typeof global !== "undefined" ? global.jQuery : null); @@ -1261,7 +1485,7 @@ var ShortcodePreview = Backbone.View.extend({ module.exports = ShortcodePreview; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],18:[function(require,module,exports){ +},{}],19:[function(require,module,exports){ (function (global){ var Backbone = (typeof window !== "undefined" ? window.Backbone : typeof global !== "undefined" ? global.Backbone : null), insertShortcodeList = require('./insert-shortcode-list.js'), @@ -1389,7 +1613,7 @@ var Shortcode_UI = Backbone.View.extend({ module.exports = Shortcode_UI; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./../utils/sui.js":9,"./edit-shortcode-form.js":11,"./insert-shortcode-list.js":13,"./media-toolbar.js":15,"./search-shortcode.js":16,"./shortcode-preview.js":17,"./tabbed-view.js":19}],19:[function(require,module,exports){ +},{"./../utils/sui.js":9,"./edit-shortcode-form.js":12,"./insert-shortcode-list.js":14,"./media-toolbar.js":16,"./search-shortcode.js":17,"./shortcode-preview.js":18,"./tabbed-view.js":20}],20:[function(require,module,exports){ (function (global){ var Backbone = (typeof window !== "undefined" ? window.Backbone : typeof global !== "undefined" ? global.Backbone : null); var sui = require('./../utils/sui.js'); diff --git a/js/src/collections/shortcode-attributes.js b/js/src/collections/shortcode-attributes.js index f8e68567..bd1f78e9 100644 --- a/js/src/collections/shortcode-attributes.js +++ b/js/src/collections/shortcode-attributes.js @@ -11,7 +11,8 @@ var ShortcodeAttributes = Backbone.Collection.extend({ return new this.constructor(_.map(this.models, function(m) { return m.clone(); })); - } + }, + }); module.exports = ShortcodeAttributes; diff --git a/js/src/field-attachment.js b/js/src/field-attachment.js deleted file mode 100644 index e14d9daf..00000000 --- a/js/src/field-attachment.js +++ /dev/null @@ -1,152 +0,0 @@ -var sui = require('sui-utils/sui'), - editAttributeField = require( 'sui-views/edit-attribute-field' ), - $ = require('jquery'); - -// Cache attachment IDs for quicker loading. -var iDCache = {}; - -sui.views.editAttributeFieldAttachment = editAttributeField.extend( { - - render: function() { - - var model = this.model; - - // Set model default values. - for ( var arg in ShortcakeImageFieldData.defaultArgs ) { - if ( ! model.get( arg ) ) { - model.set( arg, ShortcakeImageFieldData.defaultArgs[ arg ] ); - } - } - - this.$el.html( this.template( model.toJSON() ) ); - - var $container = this.$el.find( '.shortcake-attachment-preview' ); - var $addButton = $container.find( 'button.add' ); - var $removeButton = $container.find( 'button.remove' ); - - var frame = wp.media( { - multiple: false, - title: model.get( 'frameTitle' ), - library: { - type: model.get( 'libraryType' ), - }, - } ); - - /** - * Update the field attachment. - * Re-renders UI. - * If ID is empty - does nothing. - * - * @param {int} id Attachment ID - */ - var updateAttachment = function( id ) { - - if ( ! id ) { - return; - } - - model.set( 'value', id ); - - if ( iDCache[ id ] ) { - renderPreview( iDCache[ id ] ); - return; - } - - $container.addClass( 'loading' ); - - wp.ajax.post( 'get-attachment', { - 'id': id - } ).done( function( attachment ) { - // Cache for later. - iDCache[ id ] = attachment; - renderPreview( attachment ); - $container.removeClass( 'loading' ); - } ); - - } - - /** - * Renders attachment preview in field. - * @param {object} attachment model - * @return null - */ - var renderPreview = function( attachment ) { - - var $thumbnail = $('
'); - - if ( 'image' !== attachment.type ) { - - $( '', { - src: attachment.icon, - alt: attachment.title, - } ).appendTo( $thumbnail ); - - $( '
', { - class: 'filename', - html: '
' + attachment.title + '
', - } ).appendTo( $thumbnail ); - - } else { - - attachmentThumb = (typeof attachment.sizes.thumbnail !== 'undefined') ? - attachment.sizes.thumbnail : - _.first( _.sortBy( attachment.sizes, 'width' ) ); - - $( '', { - src: attachmentThumb.url, - width: attachmentThumb.width, - height: attachmentThumb.height, - alt: attachment.alt, - } ) .appendTo( $thumbnail ) - - } - - $thumbnail.find( 'img' ).wrap( '
' ); - $container.append( $thumbnail ); - $container.toggleClass( 'has-attachment', true ); - - } - - /** - * Remove the attachment. - * Render preview & Update the model. - */ - var removeAttachment = function() { - - model.set( 'value', null ); - - $container.toggleClass( 'has-attachment', false ); - $container.toggleClass( 'has-attachment', false ); - $container.find( '.thumbnail' ).remove(); - } - - // Add initial Attachment if available. - updateAttachment( model.get( 'value' ) ); - - // Remove file when the button is clicked. - $removeButton.click( function(e) { - e.preventDefault(); - removeAttachment(); - }); - - // Open media frame when add button is clicked - $addButton.click( function(e) { - e.preventDefault(); - frame.open(); - } ); - - // Update the attachment when an item is selected. - frame.on( 'select', function() { - - var selection = frame.state().get('selection'); - attachment = selection.first(); - - updateAttachment( attachment.id ); - - frame.close(); - - }); - - } - -} ); diff --git a/js/src/models/shortcode.js b/js/src/models/shortcode.js index 52da7498..9a2f4a2b 100644 --- a/js/src/models/shortcode.js +++ b/js/src/models/shortcode.js @@ -95,8 +95,7 @@ Shortcode = Backbone.Model.extend({ return template; - } - + }, }); module.exports = Shortcode; diff --git a/js/src/views/edit-attribute-field-attachment.js b/js/src/views/edit-attribute-field-attachment.js new file mode 100644 index 00000000..6a48e134 --- /dev/null +++ b/js/src/views/edit-attribute-field-attachment.js @@ -0,0 +1,186 @@ +var sui = require('sui-utils/sui'); + +var editAttributeFieldAttachment = sui.views.editAttributeField.extend( { + + events: { + 'click .add' : '_openMediaFrame', + 'click .remove' : '_removeAttachment', + 'selectAttachment' : '_selectAttachment', + }, + + /** + * Update the field attachment. + * Re-renders UI. + * If ID is empty - does nothing. + * + * @param {int} id Attachment ID + */ + updateValue: function( id ) { + + if ( ! id ) { + return; + } + + this.setValue( id ); + + var self = this; + + if ( editAttributeFieldAttachment.getFromCache( id ) ) { + self._renderPreview( this._getFromCache( id ) ); + return; + + // Call the updateValue() function, to trigger any listeners + // hooked on it. + self.triggerCallbacks(); + } + + this.$container.addClass( 'loading' ); + + wp.ajax.post( 'get-attachment', { + 'id': id + } ).done( function( attachment ) { + // Cache for later. + editAttributeFieldAttachment.setInCache( id, attachment ); + self._renderPreview( attachment ); + self.$container.removeClass( 'loading' ); + + // Call the updateValue() function, to trigger any listeners + // hooked on it. + self.triggerCallbacks(); + } ); + }, + + render: function() { + + // Set model default values. + for ( var arg in ShortcakeImageFieldData.defaultArgs ) { + if ( ! this.model.get( arg ) ) { + this.model.set( arg, ShortcakeImageFieldData.defaultArgs[ arg ] ); + } + } + + this.$el.html( this.template( this.model.toJSON() ) ); + + this.$container = this.$el.find( '.shortcake-attachment-preview' ); + var $addButton = this.$container.find( 'button.add' ); + + this.frame = wp.media( { + multiple: false, + title: this.model.get( 'frameTitle' ), + library: { + type: this.model.get( 'libraryType' ), + }, + } ); + + // Add initial Attachment if available. + this.updateValue( this.model.get( 'value' ) ); + + }, + + /** + * Renders attachment preview in field. + * @param {object} attachment model + * @return null + */ + _renderPreview: function( attachment ) { + + var $thumbnail = jQuery('
'); + + if ( 'image' !== attachment.type ) { + + jQuery( '', { + src: attachment.icon, + alt: attachment.title, + } ).appendTo( $thumbnail ); + + jQuery( '
', { + class: 'filename', + html: '
' + attachment.title + '
', + } ).appendTo( $thumbnail ); + + } else { + + attachmentThumb = (typeof attachment.sizes.thumbnail !== 'undefined') ? + attachment.sizes.thumbnail : + _.first( _.sortBy( attachment.sizes, 'width' ) ); + + jQuery( '', { + src: attachmentThumb.url, + width: attachmentThumb.width, + height: attachmentThumb.height, + alt: attachment.alt, + } ) .appendTo( $thumbnail ) + + } + + $thumbnail.find( 'img' ).wrap( '
' ); + this.$container.append( $thumbnail ); + this.$container.toggleClass( 'has-attachment', true ); + + }, + + /** + * Open media frame when add button is clicked. + * + */ + _openMediaFrame: function(e) { + e.preventDefault(); + this.frame.open(); + + var self = this; + this.frame.on( 'select', function() { + self.$el.trigger( 'selectAttachment' ); + } ); + + }, + + /** + * When an attachment is selected from the media frame, update the model value. + * + */ + _selectAttachment: function(e) { + var selection = this.frame.state().get('selection'); + attachment = selection.first(); + + this.updateValue( attachment.id ); + this.frame.close(); + }, + + /** + * Remove the attachment. + * Render preview & Update the model. + */ + _removeAttachment: function(e) { + e.preventDefault(); + + this.model.set( 'value', null ); + + this.$container.toggleClass( 'has-attachment', false ); + this.$container.find( '.thumbnail' ).remove(); + }, + +}, { + + _idCache: {}, + + /** + * Store attachments in a cache for quicker loading. + */ + setInCache: function( id, attachment ) { + this._idCache[ id ] = attachment; + }, + + /** + * Retrieve an attachment from the cache. + */ + getFromCache: function( id ){ + if ( 'undefined' === typeof this._idCache[ id ] ) { + return false; + } + return this._idCache[ id ]; + }, + +}); + +module.exports = sui.views.editAttributeFieldAttachment = editAttributeFieldAttachment; + diff --git a/js/src/views/edit-attribute-field.js b/js/src/views/edit-attribute-field.js index a1fb86a4..7d5b200b 100644 --- a/js/src/views/edit-attribute-field.js +++ b/js/src/views/edit-attribute-field.js @@ -7,15 +7,15 @@ var editAttributeField = Backbone.View.extend( { tagName: "div", events: { - 'keyup input[type="text"]': 'updateValue', - 'keyup textarea': 'updateValue', - 'change select': 'updateValue', - 'change input[type=checkbox]': 'updateValue', - 'change input[type=radio]': 'updateValue', - 'change input[type=email]': 'updateValue', - 'change input[type=number]': 'updateValue', - 'change input[type=date]': 'updateValue', - 'change input[type=url]': 'updateValue', + 'keyup input[type="text"]': 'inputChanged', + 'keyup textarea': 'inputChanged', + 'change select': 'inputChanged', + 'change input[type=checkbox]': 'inputChanged', + 'change input[type=radio]': 'inputChanged', + 'change input[type=email]': 'inputChanged', + 'change input[type=number]': 'inputChanged', + 'change input[type=date]': 'inputChanged', + 'change input[type=url]': 'inputChanged', }, render: function() { @@ -47,7 +47,7 @@ var editAttributeField = Backbone.View.extend( { data.meta = _meta.join( ' ' ); this.$el.html( this.template( data ) ); - this.updateValue(); + this.triggerCallbacks(); return this }, @@ -59,22 +59,35 @@ var editAttributeField = Backbone.View.extend( { * then it should update the model. If a callback function is registered * for this attribute, it should be called as well. */ - updateValue: function( e ) { + inputChanged: function( e ) { - if ( this.model.get( 'attr' ) ) { - var $el = $( this.el ).find( '[name=' + this.model.get( 'attr' ) + ']' ); + if ( this.getValue( 'attr' ) ) { + var $el = this.$el.find( '[name="' + this.model.get( 'attr' ) + '"]' ); } else { - var $el = $( this.el ).find( '[name="inner_content"]' ); + var $el = this.$el.find( '[name="inner_content"]' ); } if ( 'radio' === this.model.attributes.type ) { - this.model.set( 'value', $el.filter(':checked').first().val() ); + this.setValue( $el.filter(':checked').first().val() ); } else if ( 'checkbox' === this.model.attributes.type ) { - this.model.set( 'value', $el.is( ':checked' ) ); + this.setValue( $el.is( ':checked' ) ); } else { - this.model.set( 'value', $el.val() ); + this.setValue( $el.val() ); } + this.triggerCallbacks(); + }, + + getValue: function() { + return this.model.get( 'value' ); + }, + + setValue: function( val ) { + this.model.set( 'value', val ); + }, + + triggerCallbacks: function() { + var shortcodeName = this.shortcode.attributes.shortcode_tag, attributeName = this.model.get( 'attr' ), hookName = [ shortcodeName, attributeName ].join( '.' ), @@ -99,7 +112,25 @@ var editAttributeField = Backbone.View.extend( { } -} ); +}, { + + /** + * Get an attribute field from a shortcode by name. + * + * Usage: `sui.views.editAttributeField.getField( collection, 'title')` + * + * @param array collection of editAttributeFields + * @param string attribute name + * @return editAttributeField The view corresponding to the matching field + */ + getField: function( collection, attr ) { + return _.find( collection, + function( viewModel ) { + return attr === viewModel.model.get('attr'); + } + ); + } +}); sui.views.editAttributeField = editAttributeField; module.exports = editAttributeField; diff --git a/js/src/views/edit-shortcode-form.js b/js/src/views/edit-shortcode-form.js index 9fc526f5..ff30249f 100644 --- a/js/src/views/edit-shortcode-form.js +++ b/js/src/views/edit-shortcode-form.js @@ -1,7 +1,12 @@ var wp = require('wp'), -sui = require('sui-utils/sui'), -backbone = require('backbone'), -editAttributeField = require( 'sui-views/edit-attribute-field' ); + sui = require('sui-utils/sui'), + backbone = require('backbone'), + editAttributeField = require( 'sui-views/edit-attribute-field' ), + + // Additional attribute field types: these fields are all standalone in functionality, + // but bundled here for simplicity to save an HTTP request. + editAttributeFieldAttachment = require( 'sui-views/edit-attribute-field-attachment' ); + /** * Single edit shortcode content view.