├── README.md
├── example-thing.js
└── example-thing.php
/README.md:
--------------------------------------------------------------------------------
1 | How to make a TinyMCE view in WordPress
2 | =============
3 |
4 | Example Thing!
5 |
6 | In this exercise, we will support a new shortcode: `thing`
7 |
8 | Activate the plugin to see a working example of a shortcode being parsed, edited, updated, and rendered.
9 |
10 | To get started, create a Post in the WordPress editor with the following content:
11 |
12 | `[thing]`
13 |
14 | Click it to edit.
15 |
16 | Note: you need to be running WordPress 3.9 (currently `trunk`) or later
17 |
--------------------------------------------------------------------------------
/example-thing.js:
--------------------------------------------------------------------------------
1 | /*globals window, document, $, jQuery, _, Backbone */
2 | (function ($, _, Backbone) {
3 | "use strict";
4 | var media = wp.media,
5 |
6 | ThingDetailsController = media.controller.State.extend({
7 | defaults: {
8 | id: 'thing-details',
9 | title: 'Thing Details!',
10 | toolbar: 'thing-details',
11 | content: 'thing-details',
12 | menu: 'thing-details',
13 | router: false,
14 | priority: 60
15 | },
16 |
17 | initialize: function( options ) {
18 | this.thing = options.thing;
19 | media.controller.State.prototype.initialize.apply( this, arguments );
20 | }
21 | }),
22 |
23 | ThingTooController = media.controller.State.extend({
24 | defaults: {
25 | id: 'thing-too',
26 | title: 'Thing Too!',
27 | router: false,
28 | priority: 60,
29 | toolbar: 'thing-too',
30 | content: 'thing-too',
31 | menu: 'thing-details'
32 | },
33 |
34 | initialize: function( options ) {
35 | this.thing = options.thing;
36 | media.controller.State.prototype.initialize.apply( this, arguments );
37 | }
38 | }),
39 |
40 | ThingDetailsView = media.view.Settings.AttachmentDisplay.extend({
41 | className: 'thing-details',
42 | template: media.template( 'thing-details' ),
43 | prepare: function() {
44 | return _.defaults( {
45 | model: this.model.toJSON()
46 | }, this.options );
47 | }
48 | }),
49 |
50 | ThingTooView = media.view.Settings.AttachmentDisplay.extend({
51 | className: 'thing-too',
52 | template: media.template( 'thing-too' ),
53 | prepare: function() {
54 | return _.defaults( {
55 | model: this.model.toJSON()
56 | }, this.options );
57 | }
58 | }),
59 |
60 | ThingDetailsFrame = media.view.MediaFrame.Select.extend({
61 | defaults: {
62 | id: 'thing',
63 | url: '',
64 | type: 'link',
65 | title: 'Thing!',
66 | priority: 120
67 | },
68 |
69 | initialize: function( options ) {
70 | this.thing = new Backbone.Model( options.metadata );
71 | this.options.selection = new media.model.Selection( this.thing.attachment, { multiple: false } );
72 | media.view.MediaFrame.Select.prototype.initialize.apply( this, arguments );
73 | },
74 |
75 | bindHandlers: function() {
76 | media.view.MediaFrame.Select.prototype.bindHandlers.apply( this, arguments );
77 |
78 | this.on( 'menu:create:thing-details', this.createMenu, this );
79 | this.on( 'content:render:thing-details', this.contentDetailsRender, this );
80 | this.on( 'content:render:thing-too', this.contentTooRender, this );
81 | this.on( 'menu:render:thing-details', this.menuRender, this );
82 | this.on( 'toolbar:render:thing-details', this.toolbarRender, this );
83 | this.on( 'toolbar:render:thing-too', this.toolbarTooRender, this );
84 | },
85 |
86 | contentDetailsRender: function() {
87 | var view = new ThingDetailsView({
88 | controller: this,
89 | model: this.state().thing,
90 | attachment: this.state().thing.attachment
91 | }).render();
92 |
93 | this.content.set( view );
94 | },
95 |
96 | contentTooRender: function() {
97 | var view = new ThingTooView({
98 | controller: this,
99 | model: this.thing,
100 | attachment: this.thing.attachment
101 | }).render();
102 |
103 | this.content.set( view );
104 | },
105 |
106 | menuRender: function( view ) {
107 | var lastState = this.lastState(),
108 | previous = lastState && lastState.id,
109 | frame = this;
110 |
111 | view.set({
112 | cancel: {
113 | text: 'Cancel!',
114 | priority: 20,
115 | click: function() {
116 | if ( previous ) {
117 | frame.setState( previous );
118 | } else {
119 | frame.close();
120 | }
121 | }
122 | },
123 | separateCancel: new media.View({
124 | className: 'separator',
125 | priority: 40
126 | })
127 | });
128 | },
129 |
130 | toolbarRender: function() {
131 | this.toolbar.set( new media.view.Toolbar({
132 | controller: this,
133 | items: {
134 | button: {
135 | style: 'primary',
136 | text: 'Update Thing!',
137 | priority: 80,
138 | click: function() {
139 | var controller = this.controller;
140 | controller.close();
141 | controller.state().trigger( 'update', controller.thing.toJSON() );
142 | controller.setState( controller.options.state );
143 | controller.reset();
144 | }
145 | }
146 | }
147 | }) );
148 | },
149 |
150 | toolbarTooRender: function() {
151 | this.toolbar.set( new media.view.Toolbar({
152 | controller: this,
153 | items: {
154 | button: {
155 | style: 'primary',
156 | text: 'Update Thing Too!',
157 | priority: 80,
158 | click: function() {
159 | var controller = this.controller;
160 | controller.state().trigger( 'thing-too', controller.thing.toJSON() );
161 | controller.setState( controller.options.state );
162 | controller.reset();
163 | }
164 | }
165 | }
166 | }) );
167 | },
168 |
169 | createStates: function() {
170 | this.states.add([
171 | new ThingDetailsController( {
172 | thing: this.thing
173 | } ),
174 |
175 | new ThingTooController( {
176 | thing: this.thing
177 | } )
178 | ]);
179 | }
180 | }),
181 |
182 | thing = {
183 | coerce : media.coerce,
184 |
185 | defaults : {
186 | name : '',
187 | color : ''
188 | },
189 |
190 | edit : function ( data ) {
191 | var frame, shortcode = wp.shortcode.next( 'thing', data ).shortcode;
192 |
193 | frame = new ThingDetailsFrame({
194 | frame: 'thing',
195 | state: 'thing-details',
196 | metadata: _.defaults( shortcode.attrs.named, thing.defaults )
197 | });
198 |
199 | return frame;
200 | },
201 |
202 | shortcode : function( model ) {
203 | var self = this, content;
204 |
205 | _.each( thing.defaults, function( value, key ) {
206 | model[ key ] = self.coerce( model, key );
207 |
208 | if ( value === model[ key ] ) {
209 | delete model[ key ];
210 | }
211 | });
212 |
213 | content = model.content;
214 | delete model.content;
215 |
216 | return new wp.shortcode({
217 | tag: 'thing',
218 | attrs: model,
219 | content: content
220 | });
221 | }
222 | };
223 |
224 | wp.mce.views.register( 'thing', {
225 |
226 | View: {
227 | className: 'editor-thing',
228 |
229 | template: media.template( 'editor-thing' ),
230 |
231 | initialize: function( options ) {
232 | this.shortcode = options.shortcode;
233 | },
234 |
235 | getHtml: function() {
236 | return this.template( _.defaults(
237 | this.shortcode.attrs.named,
238 | thing.defaults
239 | ) );
240 | }
241 | },
242 |
243 | edit: function( node ) {
244 | var self = this, frame, data;
245 |
246 | data = window.decodeURIComponent( $( node ).attr('data-wpview-text') );
247 | frame = thing.edit( data );
248 | frame.state('thing-details').on( 'update', function( selection ) {
249 | var shortcode = thing.shortcode( selection ).string();
250 | $( node ).attr( 'data-wpview-text', window.encodeURIComponent( shortcode ) );
251 | wp.mce.views.refreshView( self, shortcode, true );
252 | frame.detach();
253 | });
254 | frame.open();
255 | }
256 |
257 | } );
258 |
259 | }(jQuery, _, Backbone));
--------------------------------------------------------------------------------
/example-thing.php:
--------------------------------------------------------------------------------
1 |
36 |
50 |
51 |
58 |
59 |
62 |