Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cb535f6
Use postMessage as default transport for core widgets
westonruter Nov 15, 2013
fdc8e78
Listen for postMessage updates to widget settings
westonruter Nov 16, 2013
a626f4e
Invoke Ajax request to render widget for injection
westonruter Nov 17, 2013
c97f524
Replace old widget with newly-rendered widget in DOM without refresh
westonruter Nov 17, 2013
e4bcbe2
Render widgets on current REQUEST_URI not admin-ajax
westonruter Nov 18, 2013
83dbf4f
Allow removed widget to be restored with postMessage
westonruter Nov 18, 2013
ff4dc47
Allow new widgets to be added to sidebars with postMessage
westonruter Nov 19, 2013
0fa7e97
Sort widgets with postMessage
westonruter Nov 22, 2013
458c111
Enable postMessage updates based on opt-in availability
westonruter Nov 25, 2013
1632a2d
Fix addition of newly-added uninitialized widgets in preview
westonruter Dec 2, 2013
fe166ca
Capture preview and previewer, allow preview to trigger refresh
westonruter Dec 7, 2013
e05b9a9
Trigger refresh of preview when last widget removed from sidebar
westonruter Dec 8, 2013
4bbfe9a
Remove unused JS function setting_id_to_widget_id()
westonruter Dec 8, 2013
5ff9ea3
Show spinner when updating widgets via postMessage
westonruter Dec 8, 2013
9e55115
Fix widget sidebar reassignment postMessage enabled
westonruter Dec 8, 2013
d809152
Add live preview theme support for bundled themes
westonruter Dec 8, 2013
c5eb254
Fix unnecessary preview refresh when adding a widget
westonruter Dec 8, 2013
75dbd04
Simplify conditions for twentythirteen theme support JS
westonruter Dec 8, 2013
c29da05
Opt-in to postMessage Ajax previews in twentyfourteen
westonruter Dec 9, 2013
ca95ff7
Simplify widget live-previewable opt-in filter to use boolean
westonruter Dec 9, 2013
35040cf
Fix initial render of newly added widget without live previewability
westonruter Dec 9, 2013
b4acd08
Merge branch 'master' into issue-37-postmessage
westonruter Dec 12, 2013
a5d1068
Disable scrollIntoView because of jarring experience
westonruter Dec 12, 2013
4257046
Merge branch 'master' into issue-37-postmessage
westonruter Dec 14, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions theme-support/twentythirteen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*global jQuery, wp */
jQuery( function ($) {
wp.customize.bind( 'sidebar-updated', function ( sidebar_id ) {
if ( 'sidebar-1' === sidebar_id && $.isFunction( $.fn.masonry ) ) {
var widget_area = $( '#secondary .widget-area' );
widget_area.masonry( 'reloadItems' );
widget_area.masonry();
}
} );
} );
286 changes: 285 additions & 1 deletion widget-customizer-preview.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
/*global jQuery, WidgetCustomizerPreview_exports */
/*global jQuery, WidgetCustomizerPreview_exports, _ */
/*exported WidgetCustomizerPreview */
var WidgetCustomizerPreview = (function ($) {
'use strict';

var self = {
rendered_sidebars: [],
sidebars_eligible_for_post_message: {},
rendered_widgets: [], // @todo only used once; not really needed as we can just loop over sidebars_widgets
widgets_eligible_for_post_message: {},
registered_sidebars: {},
widget_selectors: [],
render_widget_ajax_action: null,
render_widget_nonce_value: null,
render_widget_nonce_post_key: null,
preview: null,
i18n: {},

init: function () {
this.buildWidgetSelectors();
this.toggleSections();
this.highlightControls();
this.livePreview();
},

/**
Expand Down Expand Up @@ -73,11 +81,287 @@ var WidgetCustomizerPreview = (function ($) {
control.container.find(':input:visible:first').focus();
}
});
},

/**
* if the containing sidebar is eligible, and if there are sibling widgets the sidebar currently rendered
* @param {String} sidebar_id
* @return {Boolean}
*/
sidebarCanLivePreview: function ( sidebar_id ) {
if ( ! self.current_theme_supports ) {
return false;
}
if ( ! self.sidebars_eligible_for_post_message[sidebar_id] ) {
return false;
}
var widget_ids = wp.customize( sidebar_id_to_setting_id( sidebar_id ) )();
var rendered_widget_ids = _( widget_ids ).filter( function ( widget_id ) {
return 0 !== $( '#' + widget_id ).length;
} );
if ( rendered_widget_ids.length === 0 ) {
return false;
}
return true;
},


/**
* We can only know if a sidebar can be live-previewed by letting the
* preview tell us, so this updates the parent's transports to
* postMessage when it is available. If there is a switch from
* postMessage to refresh, the preview window will request a refresh.
* @param {String} sidebar_id
*/
refreshTransports: function () {
var changed_to_refresh = false;
$.each( self.rendered_sidebars, function ( i, sidebar_id ) {
var setting_id = sidebar_id_to_setting_id( sidebar_id );
var setting = parent.wp.customize( setting_id );
var sidebar_transport = self.sidebarCanLivePreview( sidebar_id ) ? 'postMessage' : 'refresh';
if ( 'refresh' === sidebar_transport && 'postMessage' === setting.transport ) {
changed_to_refresh = true;
}
setting.transport = sidebar_transport;

var widget_ids = wp.customize( setting_id )();
$.each( widget_ids, function ( i, widget_id ){
var setting_id = widget_id_to_setting_id( widget_id );
var setting = parent.wp.customize( setting_id );
var widget_transport = 'refresh';
var id_base = widget_id_to_base( widget_id );
if ( self.current_theme_supports && sidebar_transport === 'postMessage' && self.widgets_eligible_for_post_message[id_base] ) {
widget_transport = 'postMessage';
}
if ( 'refresh' === widget_transport && 'postMessage' === setting.transport ) {
changed_to_refresh = true;
}
setting.transport = widget_transport;
} );
} );
if ( changed_to_refresh ) {
self.preview.send( 'refresh' );
}
},

/**
*
*/
livePreview: function () {
var already_bound_widgets = {};

var bind_widget_setting = function( widget_id ) {
var setting_id = widget_id_to_setting_id( widget_id );
var binder = function( value ) {
already_bound_widgets[widget_id] = true;
var update_count = 0;
value.bind( function( to, from ) {
// Workaround for http://core.trac.wordpress.org/ticket/26061;
// once fixed, eliminate initial_value, update_count, and this conditional
update_count += 1;
if ( 1 === update_count && _.isEqual( from, to ) ) {
return;
}

var widget_setting_id = widget_id_to_setting_id( widget_id );
if ( parent.wp.customize( widget_setting_id ).transport !== 'postMessage' ) {
return;
}

var sidebar_id = null;
var sidebar_widgets = [];
wp.customize.each( function ( setting, setting_id ) {
var matches = setting_id.match( /^sidebars_widgets\[(.+)\]/ );
if ( matches && setting().indexOf( widget_id ) !== -1 ) {
sidebar_id = matches[1];
sidebar_widgets = setting();
}
} );
if ( ! sidebar_id ) {
throw new Error( 'Widget does not exist in a sidebar.' );
}

var data = {
widget_customizer_render_widget: 1,
action: self.render_widget_ajax_action,
widget_id: widget_id,
setting_id: setting_id,
instance: JSON.stringify( to )
};
var customized = {};
customized[ sidebar_id_to_setting_id( sidebar_id ) ] = sidebar_widgets;
customized[setting_id] = to;
data.customized = JSON.stringify(customized);
data[self.render_widget_nonce_post_key] = self.render_widget_nonce_value;

$.post( self.request_uri, data, function ( r ) {
if ( ! r.success ) {
throw new Error( r.data && r.data.message ? r.data.message : 'FAIL' );
}

// @todo Fire jQuery event to indicate that a widget was updated; here widgets can re-initialize them if they support live widgets
var old_widget = $( '#' + widget_id );
var new_widget = $( r.data.rendered_widget );
if ( new_widget.length && old_widget.length ) {
old_widget.replaceWith( new_widget );
}
else if ( ! new_widget.length && old_widget.length ) {
old_widget.remove();
}
else if ( new_widget.length && ! old_widget.length ) {
var sidebar_widgets = wp.customize( sidebar_id_to_setting_id( r.data.sidebar_id ) )();
var position = sidebar_widgets.indexOf( widget_id );
if ( -1 === position ) {
throw new Error( 'Unable to determine new widget position in sidebar' );
}
if ( sidebar_widgets.length === 1 ) {
throw new Error( 'Unexpected postMessage for adding first widget to sidebar; refresh must be used instead.' );
}
if ( position > 0 ) {
var before_widget = $( '#' + sidebar_widgets[ position - 1 ] );
before_widget.after( new_widget );
}
else {
var after_widget = $( '#' + sidebar_widgets[ position + 1 ] );
after_widget.before( new_widget );
}
}
self.preview.send( 'widget-updated', widget_id );
wp.customize.trigger( 'sidebar-updated', sidebar_id );
wp.customize.trigger( 'widget-updated', widget_id );
self.refreshTransports();
} );
} );
};
wp.customize( setting_id, binder );
already_bound_widgets[setting_id] = binder;
};

$.each( self.rendered_sidebars, function ( i, sidebar_id ) {
var setting_id = sidebar_id_to_setting_id( sidebar_id );
wp.customize( setting_id, function( value ) {
var update_count = 0;
value.bind( function( to, from ) {
// Workaround for http://core.trac.wordpress.org/ticket/26061;
// once fixed, eliminate initial_value, update_count, and this conditional
update_count += 1;
if ( 1 === update_count && _.isEqual( from, to ) ) {
return;
}

// Sort widgets
// @todo instead of appending to the parent, we should append relative to the first widget found
$.each( to, function ( i, widget_id ) {
var widget = $( '#' + widget_id );
widget.parent().append( widget );
} );

// Create settings for newly-created widgets
$.each( to, function ( i, widget_id ) {
var setting_id = widget_id_to_setting_id( widget_id );
var setting = wp.customize( setting_id );
if ( ! setting ) {
setting = wp.customize.create( setting_id, {} );
}

// @todo Is there another way to check if we bound?
if ( ! already_bound_widgets[widget_id] ) {
bind_widget_setting( widget_id );
}

// Force the callback to fire if this widget is newly-added
if ( from.indexOf( widget_id ) === -1 ) {
self.refreshTransports();
var parent_setting = parent.wp.customize( setting_id );
if ( 'postMessage' === parent_setting.transport ) {
setting.callbacks.fireWith( setting, [ setting(), null ] );
} else {
self.preview.send( 'refresh' );
}
}
} );

// Remove widgets (their DOM element and their setting) when removed from sidebar
$.each( from, function ( i, old_widget_id ) {
if ( -1 === to.indexOf( old_widget_id ) ) {
var setting_id = widget_id_to_setting_id( old_widget_id );
if ( wp.customize.has( setting_id ) ) {
wp.customize.remove( setting_id );
// @todo WARNING: If a widget is moved to another sidebar, we need to either not do this, or force a refresh when a widget is moved to another sidebar
}
$( '#' + old_widget_id ).remove();
}
} );

// If a widget was removed so that no widgets remain rendered in sidebar, we need to disable postMessage
self.refreshTransports();
wp.customize.trigger( 'sidebar-updated', sidebar_id );
} );
} );
} );

// @todo We don't really need rendered_widgets; we can just loop over all sidebars_widgets, and get all their widget_ids
$.each( self.rendered_widgets, function ( widget_id ) {
var setting_id = widget_id_to_setting_id( widget_id );
if ( ! wp.customize.has( setting_id ) ) {
// Used to have to do this: wp.customize.create( setting_id, instance );
// Now that the settings are registered at the `wp` action, it is late enough
// for all filters to be added, e.g. sidebars_widgets for Widget Visibility
throw new Error( 'Expected customize to have registered setting for widget ' + widget_id );
}
bind_widget_setting( widget_id );
} );

// Opt-in to LivePreview
self.refreshTransports();
}
};

$.extend(self, WidgetCustomizerPreview_exports);

/**
* Capture the instance of the Preview since it is private
*/
var OldPreview = wp.customize.Preview;
wp.customize.Preview = OldPreview.extend( {
initialize: function( params, options ) {
self.preview = this;
OldPreview.prototype.initialize.call( this, params, options );
}
} );

/**
* @param {String} widget_id
* @returns {String}
*/
function widget_id_to_setting_id( widget_id ) {
var setting_id = null;
var matches = widget_id.match(/^(.+?)(?:-(\d+)?)$/);
if ( matches ) {
setting_id = 'widget_' + matches[1] + '[' + matches[2] + ']';
}
else {
setting_id = 'widget_' + widget_id;
}
return setting_id;
}

/**
* @param {String} widget_id
* @returns {String}
*/
function widget_id_to_base( widget_id ) {
return widget_id.replace( /-\d+$/, '' );
}

/**
* @param {String} sidebar_id
* @returns {string}
*/
function sidebar_id_to_setting_id( sidebar_id ) {
return 'sidebars_widgets[' + sidebar_id + ']';
}

$(function () {
self.init();
});
Expand Down
Loading