9 |
9 |
10 // Set up our namespace... |
10 // Set up our namespace... |
11 var themes, l10n; |
11 var themes, l10n; |
12 themes = wp.themes = wp.themes || {}; |
12 themes = wp.themes = wp.themes || {}; |
13 |
13 |
14 // Store the theme data and settings for organized and quick access |
14 // Store the theme data and settings for organized and quick access. |
15 // themes.data.settings, themes.data.themes, themes.data.l10n |
15 // themes.data.settings, themes.data.themes, themes.data.l10n. |
16 themes.data = _wpThemeSettings; |
16 themes.data = _wpThemeSettings; |
17 l10n = themes.data.l10n; |
17 l10n = themes.data.l10n; |
18 |
18 |
19 // Shortcut for isInstall check |
19 // Shortcut for isInstall check. |
20 themes.isInstall = !! themes.data.settings.isInstall; |
20 themes.isInstall = !! themes.data.settings.isInstall; |
21 |
21 |
22 // Setup app structure |
22 // Setup app structure. |
23 _.extend( themes, { model: {}, view: {}, routes: {}, router: {}, template: wp.template }); |
23 _.extend( themes, { model: {}, view: {}, routes: {}, router: {}, template: wp.template }); |
24 |
24 |
25 themes.Model = Backbone.Model.extend({ |
25 themes.Model = Backbone.Model.extend({ |
26 // Adds attributes to the default data coming through the .org themes api |
26 // Adds attributes to the default data coming through the .org themes api. |
27 // Map `id` to `slug` for shared code |
27 // Map `id` to `slug` for shared code. |
28 initialize: function() { |
28 initialize: function() { |
29 var description; |
29 var description; |
30 |
30 |
31 // If theme is already installed, set an attribute. |
31 if ( this.get( 'slug' ) ) { |
32 if ( _.indexOf( themes.data.installedThemes, this.get( 'slug' ) ) !== -1 ) { |
32 // If the theme is already installed, set an attribute. |
33 this.set({ installed: true }); |
33 if ( _.indexOf( themes.data.installedThemes, this.get( 'slug' ) ) !== -1 ) { |
34 } |
34 this.set({ installed: true }); |
35 |
35 } |
36 // Set the attributes |
36 |
|
37 // If the theme is active, set an attribute. |
|
38 if ( themes.data.activeTheme === this.get( 'slug' ) ) { |
|
39 this.set({ active: true }); |
|
40 } |
|
41 } |
|
42 |
|
43 // Set the attributes. |
37 this.set({ |
44 this.set({ |
38 // slug is for installation, id is for existing. |
45 // `slug` is for installation, `id` is for existing. |
39 id: this.get( 'slug' ) || this.get( 'id' ) |
46 id: this.get( 'slug' ) || this.get( 'id' ) |
40 }); |
47 }); |
41 |
48 |
42 // Map `section.description` to `description` |
49 // Map `section.description` to `description` |
43 // as the API sometimes returns it differently |
50 // as the API sometimes returns it differently. |
44 if ( this.has( 'sections' ) ) { |
51 if ( this.has( 'sections' ) ) { |
45 description = this.get( 'sections' ).description; |
52 description = this.get( 'sections' ).description; |
46 this.set({ description: description }); |
53 this.set({ description: description }); |
47 } |
54 } |
48 } |
55 } |
49 }); |
56 }); |
50 |
57 |
51 // Main view controller for themes.php |
58 // Main view controller for themes.php. |
52 // Unifies and renders all available views |
59 // Unifies and renders all available views. |
53 themes.view.Appearance = wp.Backbone.View.extend({ |
60 themes.view.Appearance = wp.Backbone.View.extend({ |
54 |
61 |
55 el: '#wpbody-content .wrap .theme-browser', |
62 el: '#wpbody-content .wrap .theme-browser', |
56 |
63 |
57 window: $( window ), |
64 window: $( window ), |
58 // Pagination instance |
65 // Pagination instance. |
59 page: 0, |
66 page: 0, |
60 |
67 |
61 // Sets up a throttler for binding to 'scroll' |
68 // Sets up a throttler for binding to 'scroll'. |
62 initialize: function( options ) { |
69 initialize: function( options ) { |
63 // Scroller checks how far the scroll position is |
70 // Scroller checks how far the scroll position is. |
64 _.bindAll( this, 'scroller' ); |
71 _.bindAll( this, 'scroller' ); |
65 |
72 |
66 this.SearchView = options.SearchView ? options.SearchView : themes.view.Search; |
73 this.SearchView = options.SearchView ? options.SearchView : themes.view.Search; |
67 // Bind to the scroll event and throttle |
74 // Bind to the scroll event and throttle |
68 // the results from this.scroller |
75 // the results from this.scroller. |
69 this.window.bind( 'scroll', _.throttle( this.scroller, 300 ) ); |
76 this.window.bind( 'scroll', _.throttle( this.scroller, 300 ) ); |
70 }, |
77 }, |
71 |
78 |
72 // Main render control |
79 // Main render control. |
73 render: function() { |
80 render: function() { |
74 // Setup the main theme view |
81 // Setup the main theme view |
75 // with the current theme collection |
82 // with the current theme collection. |
76 this.view = new themes.view.Themes({ |
83 this.view = new themes.view.Themes({ |
77 collection: this.collection, |
84 collection: this.collection, |
78 parent: this |
85 parent: this |
79 }); |
86 }); |
80 |
87 |
81 // Render search form. |
88 // Render search form. |
82 this.search(); |
89 this.search(); |
83 |
90 |
84 this.$el.removeClass( 'search-loading' ); |
91 this.$el.removeClass( 'search-loading' ); |
85 |
92 |
86 // Render and append |
93 // Render and append. |
87 this.view.render(); |
94 this.view.render(); |
88 this.$el.empty().append( this.view.el ).addClass( 'rendered' ); |
95 this.$el.empty().append( this.view.el ).addClass( 'rendered' ); |
89 }, |
96 }, |
90 |
97 |
91 // Defines search element container |
98 // Defines search element container. |
92 searchContainer: $( '.search-form' ), |
99 searchContainer: $( '.search-form' ), |
93 |
100 |
94 // Search input and view |
101 // Search input and view |
95 // for current theme collection |
102 // for current theme collection. |
96 search: function() { |
103 search: function() { |
97 var view, |
104 var view, |
98 self = this; |
105 self = this; |
99 |
106 |
100 // Don't render the search if there is only one theme |
107 // Don't render the search if there is only one theme. |
101 if ( themes.data.themes.length === 1 ) { |
108 if ( themes.data.themes.length === 1 ) { |
102 return; |
109 return; |
103 } |
110 } |
104 |
111 |
105 view = new this.SearchView({ |
112 view = new this.SearchView({ |
106 collection: self.collection, |
113 collection: self.collection, |
107 parent: this |
114 parent: this |
108 }); |
115 }); |
109 self.SearchView = view; |
116 self.SearchView = view; |
110 |
117 |
111 // Render and append after screen title |
118 // Render and append after screen title. |
112 view.render(); |
119 view.render(); |
113 this.searchContainer |
120 this.searchContainer |
114 .append( $.parseHTML( '<label class="screen-reader-text" for="wp-filter-search-input">' + l10n.search + '</label>' ) ) |
121 .append( $.parseHTML( '<label class="screen-reader-text" for="wp-filter-search-input">' + l10n.search + '</label>' ) ) |
115 .append( view.el ) |
122 .append( view.el ) |
116 .on( 'submit', function( event ) { |
123 .on( 'submit', function( event ) { |
117 event.preventDefault(); |
124 event.preventDefault(); |
118 }); |
125 }); |
119 }, |
126 }, |
120 |
127 |
121 // Checks when the user gets close to the bottom |
128 // Checks when the user gets close to the bottom |
122 // of the mage and triggers a theme:scroll event |
129 // of the mage and triggers a theme:scroll event. |
123 scroller: function() { |
130 scroller: function() { |
124 var self = this, |
131 var self = this, |
125 bottom, threshold; |
132 bottom, threshold; |
126 |
133 |
127 bottom = this.window.scrollTop() + self.window.height(); |
134 bottom = this.window.scrollTop() + self.window.height(); |
132 this.trigger( 'theme:scroll' ); |
139 this.trigger( 'theme:scroll' ); |
133 } |
140 } |
134 } |
141 } |
135 }); |
142 }); |
136 |
143 |
137 // Set up the Collection for our theme data |
144 // Set up the Collection for our theme data. |
138 // @has 'id' 'name' 'screenshot' 'author' 'authorURI' 'version' 'active' ... |
145 // @has 'id' 'name' 'screenshot' 'author' 'authorURI' 'version' 'active' ... |
139 themes.Collection = Backbone.Collection.extend({ |
146 themes.Collection = Backbone.Collection.extend({ |
140 |
147 |
141 model: themes.Model, |
148 model: themes.Model, |
142 |
149 |
143 // Search terms |
150 // Search terms. |
144 terms: '', |
151 terms: '', |
145 |
152 |
146 // Controls searching on the current theme collection |
153 // Controls searching on the current theme collection |
147 // and triggers an update event |
154 // and triggers an update event. |
148 doSearch: function( value ) { |
155 doSearch: function( value ) { |
149 |
156 |
150 // Don't do anything if we've already done this search |
157 // Don't do anything if we've already done this search. |
151 // Useful because the Search handler fires multiple times per keystroke |
158 // Useful because the Search handler fires multiple times per keystroke. |
152 if ( this.terms === value ) { |
159 if ( this.terms === value ) { |
153 return; |
160 return; |
154 } |
161 } |
155 |
162 |
156 // Updates terms with the value passed |
163 // Updates terms with the value passed. |
157 this.terms = value; |
164 this.terms = value; |
158 |
165 |
159 // If we have terms, run a search... |
166 // If we have terms, run a search... |
160 if ( this.terms.length > 0 ) { |
167 if ( this.terms.length > 0 ) { |
161 this.search( this.terms ); |
168 this.search( this.terms ); |
162 } |
169 } |
163 |
170 |
164 // If search is blank, show all themes |
171 // If search is blank, show all themes. |
165 // Useful for resetting the views when you clean the input |
172 // Useful for resetting the views when you clean the input. |
166 if ( this.terms === '' ) { |
173 if ( this.terms === '' ) { |
167 this.reset( themes.data.themes ); |
174 this.reset( themes.data.themes ); |
168 $( 'body' ).removeClass( 'no-results' ); |
175 $( 'body' ).removeClass( 'no-results' ); |
169 } |
176 } |
170 |
177 |
171 // Trigger a 'themes:update' event |
178 // Trigger a 'themes:update' event. |
172 this.trigger( 'themes:update' ); |
179 this.trigger( 'themes:update' ); |
173 }, |
180 }, |
174 |
181 |
175 // Performs a search within the collection |
182 /** |
176 // @uses RegExp |
183 * Performs a search within the collection. |
|
184 * |
|
185 * @uses RegExp |
|
186 */ |
177 search: function( term ) { |
187 search: function( term ) { |
178 var match, results, haystack, name, description, author; |
188 var match, results, haystack, name, description, author; |
179 |
189 |
180 // Start with a full collection |
190 // Start with a full collection. |
181 this.reset( themes.data.themes, { silent: true } ); |
191 this.reset( themes.data.themes, { silent: true } ); |
182 |
192 |
183 // Escape the term string for RegExp meta characters |
193 // Trim the term. |
|
194 term = term.trim(); |
|
195 |
|
196 // Escape the term string for RegExp meta characters. |
184 term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' ); |
197 term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' ); |
185 |
198 |
186 // Consider spaces as word delimiters and match the whole string |
199 // Consider spaces as word delimiters and match the whole string |
187 // so matching terms can be combined |
200 // so matching terms can be combined. |
188 term = term.replace( / /g, ')(?=.*' ); |
201 term = term.replace( / /g, ')(?=.*' ); |
189 match = new RegExp( '^(?=.*' + term + ').+', 'i' ); |
202 match = new RegExp( '^(?=.*' + term + ').+', 'i' ); |
190 |
203 |
191 // Find results |
204 // Find results. |
192 // _.filter and .test |
205 // _.filter() and .test(). |
193 results = this.filter( function( data ) { |
206 results = this.filter( function( data ) { |
194 name = data.get( 'name' ).replace( /(<([^>]+)>)/ig, '' ); |
207 name = data.get( 'name' ).replace( /(<([^>]+)>)/ig, '' ); |
195 description = data.get( 'description' ).replace( /(<([^>]+)>)/ig, '' ); |
208 description = data.get( 'description' ).replace( /(<([^>]+)>)/ig, '' ); |
196 author = data.get( 'author' ).replace( /(<([^>]+)>)/ig, '' ); |
209 author = data.get( 'author' ).replace( /(<([^>]+)>)/ig, '' ); |
197 |
210 |
212 |
225 |
213 this.reset( results ); |
226 this.reset( results ); |
214 }, |
227 }, |
215 |
228 |
216 // Paginates the collection with a helper method |
229 // Paginates the collection with a helper method |
217 // that slices the collection |
230 // that slices the collection. |
218 paginate: function( instance ) { |
231 paginate: function( instance ) { |
219 var collection = this; |
232 var collection = this; |
220 instance = instance || 0; |
233 instance = instance || 0; |
221 |
234 |
222 // Themes per instance are set at 20 |
235 // Themes per instance are set at 20. |
223 collection = _( collection.rest( 20 * instance ) ); |
236 collection = _( collection.rest( 20 * instance ) ); |
224 collection = _( collection.first( 20 ) ); |
237 collection = _( collection.first( 20 ) ); |
225 |
238 |
226 return collection; |
239 return collection; |
227 }, |
240 }, |
228 |
241 |
229 count: false, |
242 count: false, |
230 |
243 |
231 // Handles requests for more themes |
244 /* |
232 // and caches results |
245 * Handles requests for more themes and caches results. |
233 // |
246 * |
234 // When we are missing a cache object we fire an apiCall() |
247 * |
235 // which triggers events of `query:success` or `query:fail` |
248 * When we are missing a cache object we fire an apiCall() |
|
249 * which triggers events of `query:success` or `query:fail`. |
|
250 */ |
236 query: function( request ) { |
251 query: function( request ) { |
237 /** |
252 /** |
238 * @static |
253 * @static |
239 * @type Array |
254 * @type Array |
240 */ |
255 */ |
241 var queries = this.queries, |
256 var queries = this.queries, |
242 self = this, |
257 self = this, |
243 query, isPaginated, count; |
258 query, isPaginated, count; |
244 |
259 |
245 // Store current query request args |
260 // Store current query request args |
246 // for later use with the event `theme:end` |
261 // for later use with the event `theme:end`. |
247 this.currentQuery.request = request; |
262 this.currentQuery.request = request; |
248 |
263 |
249 // Search the query cache for matches. |
264 // Search the query cache for matches. |
250 query = _.find( queries, function( query ) { |
265 query = _.find( queries, function( query ) { |
251 return _.isEqual( query.request, request ); |
266 return _.isEqual( query.request, request ); |
417 |
432 |
418 $('.theme.focus').removeClass('focus'); |
433 $('.theme.focus').removeClass('focus'); |
419 $themeToFocus.addClass('focus'); |
434 $themeToFocus.addClass('focus'); |
420 }, |
435 }, |
421 |
436 |
422 // Single theme overlay screen |
437 // Single theme overlay screen. |
423 // It's shown when clicking a theme |
438 // It's shown when clicking a theme. |
424 expand: function( event ) { |
439 expand: function( event ) { |
425 var self = this; |
440 var self = this; |
426 |
441 |
427 event = event || window.event; |
442 event = event || window.event; |
428 |
443 |
429 // 'enter' and 'space' keys expand the details view when a theme is :focused |
444 // 'Enter' and 'Space' keys expand the details view when a theme is :focused. |
430 if ( event.type === 'keydown' && ( event.which !== 13 && event.which !== 32 ) ) { |
445 if ( event.type === 'keydown' && ( event.which !== 13 && event.which !== 32 ) ) { |
431 return; |
446 return; |
432 } |
447 } |
433 |
448 |
434 // Bail if the user scrolled on a touch device |
449 // Bail if the user scrolled on a touch device. |
435 if ( this.touchDrag === true ) { |
450 if ( this.touchDrag === true ) { |
436 return this.touchDrag = false; |
451 return this.touchDrag = false; |
437 } |
452 } |
438 |
453 |
439 // Prevent the modal from showing when the user clicks |
454 // Prevent the modal from showing when the user clicks |
440 // one of the direct action buttons |
455 // one of the direct action buttons. |
441 if ( $( event.target ).is( '.theme-actions a' ) ) { |
456 if ( $( event.target ).is( '.theme-actions a' ) ) { |
442 return; |
457 return; |
443 } |
458 } |
444 |
459 |
445 // Prevent the modal from showing when the user clicks one of the direct action buttons. |
460 // Prevent the modal from showing when the user clicks one of the direct action buttons. |
446 if ( $( event.target ).is( '.theme-actions a, .update-message, .button-link, .notice-dismiss' ) ) { |
461 if ( $( event.target ).is( '.theme-actions a, .update-message, .button-link, .notice-dismiss' ) ) { |
447 return; |
462 return; |
448 } |
463 } |
449 |
464 |
450 // Set focused theme to current element |
465 // Set focused theme to current element. |
451 themes.focusedTheme = this.$el; |
466 themes.focusedTheme = this.$el; |
452 |
467 |
453 this.trigger( 'theme:expand', self.model.cid ); |
468 this.trigger( 'theme:expand', self.model.cid ); |
454 }, |
469 }, |
455 |
470 |
572 self.current = self.model; |
587 self.current = self.model; |
573 }); |
588 }); |
574 |
589 |
575 }, |
590 }, |
576 |
591 |
577 // Handles .disabled classes for previous/next buttons in theme installer preview |
592 // Handles .disabled classes for previous/next buttons in theme installer preview. |
578 setNavButtonsState: function() { |
593 setNavButtonsState: function() { |
579 var $themeInstaller = $( '.theme-install-overlay' ), |
594 var $themeInstaller = $( '.theme-install-overlay' ), |
580 current = _.isUndefined( this.current ) ? this.model : this.current, |
595 current = _.isUndefined( this.current ) ? this.model : this.current, |
581 previousThemeButton = $themeInstaller.find( '.previous-theme' ), |
596 previousThemeButton = $themeInstaller.find( '.previous-theme' ), |
582 nextThemeButton = $themeInstaller.find( '.next-theme' ); |
597 nextThemeButton = $themeInstaller.find( '.next-theme' ); |
583 |
598 |
584 // Disable previous at the zero position |
599 // Disable previous at the zero position. |
585 if ( 0 === this.model.collection.indexOf( current ) ) { |
600 if ( 0 === this.model.collection.indexOf( current ) ) { |
586 previousThemeButton |
601 previousThemeButton |
587 .addClass( 'disabled' ) |
602 .addClass( 'disabled' ) |
588 .prop( 'disabled', true ); |
603 .prop( 'disabled', true ); |
589 |
604 |
590 nextThemeButton.focus(); |
605 nextThemeButton.focus(); |
591 } |
606 } |
592 |
607 |
593 // Disable next if the next model is undefined |
608 // Disable next if the next model is undefined. |
594 if ( _.isUndefined( this.model.collection.at( this.model.collection.indexOf( current ) + 1 ) ) ) { |
609 if ( _.isUndefined( this.model.collection.at( this.model.collection.indexOf( current ) + 1 ) ) ) { |
595 nextThemeButton |
610 nextThemeButton |
596 .addClass( 'disabled' ) |
611 .addClass( 'disabled' ) |
597 .prop( 'disabled', true ); |
612 .prop( 'disabled', true ); |
598 |
613 |
644 slug: $( event.target ).parents( 'div.theme' ).first().data( 'slug' ) |
659 slug: $( event.target ).parents( 'div.theme' ).first().data( 'slug' ) |
645 } ); |
660 } ); |
646 } |
661 } |
647 }); |
662 }); |
648 |
663 |
649 // Theme Details view |
664 // Theme Details view. |
650 // Set ups a modal overlay with the expanded theme data |
665 // Sets up a modal overlay with the expanded theme data. |
651 themes.view.Details = wp.Backbone.View.extend({ |
666 themes.view.Details = wp.Backbone.View.extend({ |
652 |
667 |
653 // Wrap theme data on a div.theme element |
668 // Wrap theme data on a div.theme element. |
654 className: 'theme-overlay', |
669 className: 'theme-overlay', |
655 |
670 |
656 events: { |
671 events: { |
657 'click': 'collapse', |
672 'click': 'collapse', |
658 'click .delete-theme': 'deleteTheme', |
673 'click .delete-theme': 'deleteTheme', |
659 'click .left': 'previousTheme', |
674 'click .left': 'previousTheme', |
660 'click .right': 'nextTheme', |
675 'click .right': 'nextTheme', |
661 'click #update-theme': 'updateTheme' |
676 'click #update-theme': 'updateTheme', |
662 }, |
677 'click .toggle-auto-update': 'autoupdateState' |
663 |
678 }, |
664 // The HTML template for the theme overlay |
679 |
|
680 // The HTML template for the theme overlay. |
665 html: themes.template( 'theme-single' ), |
681 html: themes.template( 'theme-single' ), |
666 |
682 |
667 render: function() { |
683 render: function() { |
668 var data = this.model.toJSON(); |
684 var data = this.model.toJSON(); |
669 this.$el.html( this.html( data ) ); |
685 this.$el.html( this.html( data ) ); |
670 // Renders active theme styles |
686 // Renders active theme styles. |
671 this.activeTheme(); |
687 this.activeTheme(); |
672 // Set up navigation events |
688 // Set up navigation events. |
673 this.navigation(); |
689 this.navigation(); |
674 // Checks screenshot size |
690 // Checks screenshot size. |
675 this.screenshotCheck( this.$el ); |
691 this.screenshotCheck( this.$el ); |
676 // Contain "tabbing" inside the overlay |
692 // Contain "tabbing" inside the overlay. |
677 this.containFocus( this.$el ); |
693 this.containFocus( this.$el ); |
678 }, |
694 }, |
679 |
695 |
680 // Adds a class to the currently active theme |
696 // Adds a class to the currently active theme |
681 // and to the overlay in detailed view mode |
697 // and to the overlay in detailed view mode. |
682 activeTheme: function() { |
698 activeTheme: function() { |
683 // Check the model has the active property |
699 // Check the model has the active property. |
684 this.$el.toggleClass( 'active', this.model.get( 'active' ) ); |
700 this.$el.toggleClass( 'active', this.model.get( 'active' ) ); |
685 }, |
701 }, |
686 |
702 |
687 // Set initial focus and constrain tabbing within the theme browser modal. |
703 // Set initial focus and constrain tabbing within the theme browser modal. |
688 containFocus: function( $el ) { |
704 containFocus: function( $el ) { |
708 } |
724 } |
709 } |
725 } |
710 }); |
726 }); |
711 }, |
727 }, |
712 |
728 |
713 // Single theme overlay screen |
729 // Single theme overlay screen. |
714 // It's shown when clicking a theme |
730 // It's shown when clicking a theme. |
715 collapse: function( event ) { |
731 collapse: function( event ) { |
716 var self = this, |
732 var self = this, |
717 scroll; |
733 scroll; |
718 |
734 |
719 event = event || window.event; |
735 event = event || window.event; |
720 |
736 |
721 // Prevent collapsing detailed view when there is only one theme available |
737 // Prevent collapsing detailed view when there is only one theme available. |
722 if ( themes.data.themes.length === 1 ) { |
738 if ( themes.data.themes.length === 1 ) { |
723 return; |
739 return; |
724 } |
740 } |
725 |
741 |
726 // Detect if the click is inside the overlay |
742 // Detect if the click is inside the overlay and don't close it |
727 // and don't close it unless the target was |
743 // unless the target was the div.back button. |
728 // the div.back button |
|
729 if ( $( event.target ).is( '.theme-backdrop' ) || $( event.target ).is( '.close' ) || event.keyCode === 27 ) { |
744 if ( $( event.target ).is( '.theme-backdrop' ) || $( event.target ).is( '.close' ) || event.keyCode === 27 ) { |
730 |
745 |
731 // Add a temporary closing class while overlay fades out |
746 // Add a temporary closing class while overlay fades out. |
732 $( 'body' ).addClass( 'closing-overlay' ); |
747 $( 'body' ).addClass( 'closing-overlay' ); |
733 |
748 |
734 // With a quick fade out animation |
749 // With a quick fade out animation. |
735 this.$el.fadeOut( 130, function() { |
750 this.$el.fadeOut( 130, function() { |
736 // Clicking outside the modal box closes the overlay |
751 // Clicking outside the modal box closes the overlay. |
737 $( 'body' ).removeClass( 'closing-overlay' ); |
752 $( 'body' ).removeClass( 'closing-overlay' ); |
738 // Handle event cleanup |
753 // Handle event cleanup. |
739 self.closeOverlay(); |
754 self.closeOverlay(); |
740 |
755 |
741 // Get scroll position to avoid jumping to the top |
756 // Get scroll position to avoid jumping to the top. |
742 scroll = document.body.scrollTop; |
757 scroll = document.body.scrollTop; |
743 |
758 |
744 // Clean the url structure |
759 // Clean the URL structure. |
745 themes.router.navigate( themes.router.baseUrl( '' ) ); |
760 themes.router.navigate( themes.router.baseUrl( '' ) ); |
746 |
761 |
747 // Restore scroll position |
762 // Restore scroll position. |
748 document.body.scrollTop = scroll; |
763 document.body.scrollTop = scroll; |
749 |
764 |
750 // Return focus to the theme div |
765 // Return focus to the theme div. |
751 if ( themes.focusedTheme ) { |
766 if ( themes.focusedTheme ) { |
752 themes.focusedTheme.focus(); |
767 themes.focusedTheme.focus(); |
753 } |
768 } |
754 }); |
769 }); |
755 } |
770 } |
756 }, |
771 }, |
757 |
772 |
758 // Handles .disabled classes for next/previous buttons |
773 // Handles .disabled classes for next/previous buttons. |
759 navigation: function() { |
774 navigation: function() { |
760 |
775 |
761 // Disable Left/Right when at the start or end of the collection |
776 // Disable Left/Right when at the start or end of the collection. |
762 if ( this.model.cid === this.model.collection.at(0).cid ) { |
777 if ( this.model.cid === this.model.collection.at(0).cid ) { |
763 this.$el.find( '.left' ) |
778 this.$el.find( '.left' ) |
764 .addClass( 'disabled' ) |
779 .addClass( 'disabled' ) |
765 .prop( 'disabled', true ); |
780 .prop( 'disabled', true ); |
766 } |
781 } |
843 self.trigger( 'theme:previous', self.model.cid ); |
878 self.trigger( 'theme:previous', self.model.cid ); |
844 return false; |
879 return false; |
845 }, |
880 }, |
846 |
881 |
847 // Checks if the theme screenshot is the old 300px width version |
882 // Checks if the theme screenshot is the old 300px width version |
848 // and adds a corresponding class if it's true |
883 // and adds a corresponding class if it's true. |
849 screenshotCheck: function( el ) { |
884 screenshotCheck: function( el ) { |
850 var screenshot, image; |
885 var screenshot, image; |
851 |
886 |
852 screenshot = el.find( '.screenshot img' ); |
887 screenshot = el.find( '.screenshot img' ); |
853 image = new Image(); |
888 image = new Image(); |
854 image.src = screenshot.attr( 'src' ); |
889 image.src = screenshot.attr( 'src' ); |
855 |
890 |
856 // Width check |
891 // Width check. |
857 if ( image.width && image.width <= 300 ) { |
892 if ( image.width && image.width <= 300 ) { |
858 el.addClass( 'small-screenshot' ); |
893 el.addClass( 'small-screenshot' ); |
859 } |
894 } |
860 } |
895 } |
861 }); |
896 }); |
862 |
897 |
863 // Theme Preview view |
898 // Theme Preview view. |
864 // Set ups a modal overlay with the expanded theme data |
899 // Sets up a modal overlay with the expanded theme data. |
865 themes.view.Preview = themes.view.Details.extend({ |
900 themes.view.Preview = themes.view.Details.extend({ |
866 |
901 |
867 className: 'wp-full-overlay expanded', |
902 className: 'wp-full-overlay expanded', |
868 el: '.theme-install-overlay', |
903 el: '.theme-install-overlay', |
869 |
904 |
1006 } ); |
1041 } ); |
1007 } |
1042 } |
1008 }); |
1043 }); |
1009 |
1044 |
1010 // Controls the rendering of div.themes, |
1045 // Controls the rendering of div.themes, |
1011 // a wrapper that will hold all the theme elements |
1046 // a wrapper that will hold all the theme elements. |
1012 themes.view.Themes = wp.Backbone.View.extend({ |
1047 themes.view.Themes = wp.Backbone.View.extend({ |
1013 |
1048 |
1014 className: 'themes wp-clearfix', |
1049 className: 'themes wp-clearfix', |
1015 $overlay: $( 'div.theme-overlay' ), |
1050 $overlay: $( 'div.theme-overlay' ), |
1016 |
1051 |
1017 // Number to keep track of scroll position |
1052 // Number to keep track of scroll position |
1018 // while in theme-overlay mode |
1053 // while in theme-overlay mode. |
1019 index: 0, |
1054 index: 0, |
1020 |
1055 |
1021 // The theme count element |
1056 // The theme count element. |
1022 count: $( '.wrap .theme-count' ), |
1057 count: $( '.wrap .theme-count' ), |
1023 |
1058 |
1024 // The live themes count |
1059 // The live themes count. |
1025 liveThemeCount: 0, |
1060 liveThemeCount: 0, |
1026 |
1061 |
1027 initialize: function( options ) { |
1062 initialize: function( options ) { |
1028 var self = this; |
1063 var self = this; |
1029 |
1064 |
1030 // Set up parent |
1065 // Set up parent. |
1031 this.parent = options.parent; |
1066 this.parent = options.parent; |
1032 |
1067 |
1033 // Set current view to [grid] |
1068 // Set current view to [grid]. |
1034 this.setView( 'grid' ); |
1069 this.setView( 'grid' ); |
1035 |
1070 |
1036 // Move the active theme to the beginning of the collection |
1071 // Move the active theme to the beginning of the collection. |
1037 self.currentTheme(); |
1072 self.currentTheme(); |
1038 |
1073 |
1039 // When the collection is updated by user input... |
1074 // When the collection is updated by user input... |
1040 this.listenTo( self.collection, 'themes:update', function() { |
1075 this.listenTo( self.collection, 'themes:update', function() { |
1041 self.parent.page = 0; |
1076 self.parent.page = 0; |
1077 // Bail if the filesystem credentials dialog is shown. |
1112 // Bail if the filesystem credentials dialog is shown. |
1078 if ( $( '#request-filesystem-credentials-dialog' ).is( ':visible' ) ) { |
1113 if ( $( '#request-filesystem-credentials-dialog' ).is( ':visible' ) ) { |
1079 return; |
1114 return; |
1080 } |
1115 } |
1081 |
1116 |
1082 // Pressing the right arrow key fires a theme:next event |
1117 // Pressing the right arrow key fires a theme:next event. |
1083 if ( event.keyCode === 39 ) { |
1118 if ( event.keyCode === 39 ) { |
1084 self.overlay.nextTheme(); |
1119 self.overlay.nextTheme(); |
1085 } |
1120 } |
1086 |
1121 |
1087 // Pressing the left arrow key fires a theme:previous event |
1122 // Pressing the left arrow key fires a theme:previous event. |
1088 if ( event.keyCode === 37 ) { |
1123 if ( event.keyCode === 37 ) { |
1089 self.overlay.previousTheme(); |
1124 self.overlay.previousTheme(); |
1090 } |
1125 } |
1091 |
1126 |
1092 // Pressing the escape key fires a theme:collapse event |
1127 // Pressing the escape key fires a theme:collapse event. |
1093 if ( event.keyCode === 27 ) { |
1128 if ( event.keyCode === 27 ) { |
1094 self.overlay.collapse( event ); |
1129 self.overlay.collapse( event ); |
1095 } |
1130 } |
1096 }); |
1131 }); |
1097 }, |
1132 }, |
1098 |
1133 |
1099 // Manages rendering of theme pages |
1134 // Manages rendering of theme pages |
1100 // and keeping theme count in sync |
1135 // and keeping theme count in sync. |
1101 render: function() { |
1136 render: function() { |
1102 // Clear the DOM, please |
1137 // Clear the DOM, please. |
1103 this.$el.empty(); |
1138 this.$el.empty(); |
1104 |
1139 |
1105 // If the user doesn't have switch capabilities |
1140 // If the user doesn't have switch capabilities or there is only one theme |
1106 // or there is only one theme in the collection |
1141 // in the collection, render the detailed view of the active theme. |
1107 // render the detailed view of the active theme |
|
1108 if ( themes.data.themes.length === 1 ) { |
1142 if ( themes.data.themes.length === 1 ) { |
1109 |
1143 |
1110 // Constructs the view |
1144 // Constructs the view. |
1111 this.singleTheme = new themes.view.Details({ |
1145 this.singleTheme = new themes.view.Details({ |
1112 model: this.collection.models[0] |
1146 model: this.collection.models[0] |
1113 }); |
1147 }); |
1114 |
1148 |
1115 // Render and apply a 'single-theme' class to our container |
1149 // Render and apply a 'single-theme' class to our container. |
1116 this.singleTheme.render(); |
1150 this.singleTheme.render(); |
1117 this.$el.addClass( 'single-theme' ); |
1151 this.$el.addClass( 'single-theme' ); |
1118 this.$el.append( this.singleTheme.el ); |
1152 this.$el.append( this.singleTheme.el ); |
1119 } |
1153 } |
1120 |
1154 |
1121 // Generate the themes |
1155 // Generate the themes using page instance |
1122 // Using page instance |
1156 // while checking the collection has items. |
1123 // While checking the collection has items |
|
1124 if ( this.options.collection.size() > 0 ) { |
1157 if ( this.options.collection.size() > 0 ) { |
1125 this.renderThemes( this.parent.page ); |
1158 this.renderThemes( this.parent.page ); |
1126 } |
1159 } |
1127 |
1160 |
1128 // Display a live theme count for the collection |
1161 // Display a live theme count for the collection. |
1129 this.liveThemeCount = this.collection.count ? this.collection.count : this.collection.length; |
1162 this.liveThemeCount = this.collection.count ? this.collection.count : this.collection.length; |
1130 this.count.text( this.liveThemeCount ); |
1163 this.count.text( this.liveThemeCount ); |
1131 |
1164 |
1132 /* |
1165 /* |
1133 * In the theme installer the themes count is already announced |
1166 * In the theme installer the themes count is already announced |
1137 this.announceSearchResults( this.liveThemeCount ); |
1170 this.announceSearchResults( this.liveThemeCount ); |
1138 } |
1171 } |
1139 }, |
1172 }, |
1140 |
1173 |
1141 // Iterates through each instance of the collection |
1174 // Iterates through each instance of the collection |
1142 // and renders each theme module |
1175 // and renders each theme module. |
1143 renderThemes: function( page ) { |
1176 renderThemes: function( page ) { |
1144 var self = this; |
1177 var self = this; |
1145 |
1178 |
1146 self.instance = self.collection.paginate( page ); |
1179 self.instance = self.collection.paginate( page ); |
1147 |
1180 |
1148 // If we have no more themes bail |
1181 // If we have no more themes, bail. |
1149 if ( self.instance.size() === 0 ) { |
1182 if ( self.instance.size() === 0 ) { |
1150 // Fire a no-more-themes event. |
1183 // Fire a no-more-themes event. |
1151 this.parent.trigger( 'theme:end' ); |
1184 this.parent.trigger( 'theme:end' ); |
1152 return; |
1185 return; |
1153 } |
1186 } |
1154 |
1187 |
1155 // Make sure the add-new stays at the end |
1188 // Make sure the add-new stays at the end. |
1156 if ( ! themes.isInstall && page >= 1 ) { |
1189 if ( ! themes.isInstall && page >= 1 ) { |
1157 $( '.add-new-theme' ).remove(); |
1190 $( '.add-new-theme' ).remove(); |
1158 } |
1191 } |
1159 |
1192 |
1160 // Loop through the themes and setup each theme view |
1193 // Loop through the themes and setup each theme view. |
1161 self.instance.each( function( theme ) { |
1194 self.instance.each( function( theme ) { |
1162 self.theme = new themes.view.Theme({ |
1195 self.theme = new themes.view.Theme({ |
1163 model: theme, |
1196 model: theme, |
1164 parent: self |
1197 parent: self |
1165 }); |
1198 }); |
1166 |
1199 |
1167 // Render the views... |
1200 // Render the views... |
1168 self.theme.render(); |
1201 self.theme.render(); |
1169 // and append them to div.themes |
1202 // ...and append them to div.themes. |
1170 self.$el.append( self.theme.el ); |
1203 self.$el.append( self.theme.el ); |
1171 |
1204 |
1172 // Binds to theme:expand to show the modal box |
1205 // Binds to theme:expand to show the modal box |
1173 // with the theme details |
1206 // with the theme details. |
1174 self.listenTo( self.theme, 'theme:expand', self.expand, self ); |
1207 self.listenTo( self.theme, 'theme:expand', self.expand, self ); |
1175 }); |
1208 }); |
1176 |
1209 |
1177 // 'Add new theme' element shown at the end of the grid |
1210 // 'Add new theme' element shown at the end of the grid. |
1178 if ( ! themes.isInstall && themes.data.settings.canInstall ) { |
1211 if ( ! themes.isInstall && themes.data.settings.canInstall ) { |
1179 this.$el.append( '<div class="theme add-new-theme"><a href="' + themes.data.settings.installURI + '"><div class="theme-screenshot"><span></span></div><h2 class="theme-name">' + l10n.addNew + '</h2></a></div>' ); |
1212 this.$el.append( '<div class="theme add-new-theme"><a href="' + themes.data.settings.installURI + '"><div class="theme-screenshot"><span></span></div><h2 class="theme-name">' + l10n.addNew + '</h2></a></div>' ); |
1180 } |
1213 } |
1181 |
1214 |
1182 this.parent.page++; |
1215 this.parent.page++; |
1183 }, |
1216 }, |
1184 |
1217 |
1185 // Grabs current theme and puts it at the beginning of the collection |
1218 // Grabs current theme and puts it at the beginning of the collection. |
1186 currentTheme: function() { |
1219 currentTheme: function() { |
1187 var self = this, |
1220 var self = this, |
1188 current; |
1221 current; |
1189 |
1222 |
1190 current = self.collection.findWhere({ active: true }); |
1223 current = self.collection.findWhere({ active: true }); |
1191 |
1224 |
1192 // Move the active theme to the beginning of the collection |
1225 // Move the active theme to the beginning of the collection. |
1193 if ( current ) { |
1226 if ( current ) { |
1194 self.collection.remove( current ); |
1227 self.collection.remove( current ); |
1195 self.collection.add( current, { at:0 } ); |
1228 self.collection.add( current, { at:0 } ); |
1196 } |
1229 } |
1197 }, |
1230 }, |
1198 |
1231 |
1199 // Sets current view |
1232 // Sets current view. |
1200 setView: function( view ) { |
1233 setView: function( view ) { |
1201 return view; |
1234 return view; |
1202 }, |
1235 }, |
1203 |
1236 |
1204 // Renders the overlay with the ThemeDetails view |
1237 // Renders the overlay with the ThemeDetails view. |
1205 // Uses the current model data |
1238 // Uses the current model data. |
1206 expand: function( id ) { |
1239 expand: function( id ) { |
1207 var self = this, $card, $modal; |
1240 var self = this, $card, $modal; |
1208 |
1241 |
1209 // Set the current theme model |
1242 // Set the current theme model. |
1210 this.model = self.collection.get( id ); |
1243 this.model = self.collection.get( id ); |
1211 |
1244 |
1212 // Trigger a route update for the current model |
1245 // Trigger a route update for the current model. |
1213 themes.router.navigate( themes.router.baseUrl( themes.router.themePath + this.model.id ) ); |
1246 themes.router.navigate( themes.router.baseUrl( themes.router.themePath + this.model.id ) ); |
1214 |
1247 |
1215 // Sets this.view to 'detail' |
1248 // Sets this.view to 'detail'. |
1216 this.setView( 'detail' ); |
1249 this.setView( 'detail' ); |
1217 $( 'body' ).addClass( 'modal-open' ); |
1250 $( 'body' ).addClass( 'modal-open' ); |
1218 |
1251 |
1219 // Set up the theme details view |
1252 // Set up the theme details view. |
1220 this.overlay = new themes.view.Details({ |
1253 this.overlay = new themes.view.Details({ |
1221 model: self.model |
1254 model: self.model |
1222 }); |
1255 }); |
1223 |
1256 |
1224 this.overlay.render(); |
1257 this.overlay.render(); |
1238 } |
1271 } |
1239 } |
1272 } |
1240 |
1273 |
1241 this.$overlay.html( this.overlay.el ); |
1274 this.$overlay.html( this.overlay.el ); |
1242 |
1275 |
1243 // Bind to theme:next and theme:previous |
1276 // Bind to theme:next and theme:previous triggered by the arrow keys. |
1244 // triggered by the arrow keys |
1277 // Keep track of the current model so we can infer an index position. |
1245 // |
|
1246 // Keep track of the current model so we |
|
1247 // can infer an index position |
|
1248 this.listenTo( this.overlay, 'theme:next', function() { |
1278 this.listenTo( this.overlay, 'theme:next', function() { |
1249 // Renders the next theme on the overlay |
1279 // Renders the next theme on the overlay. |
1250 self.next( [ self.model.cid ] ); |
1280 self.next( [ self.model.cid ] ); |
1251 |
1281 |
1252 }) |
1282 }) |
1253 .listenTo( this.overlay, 'theme:previous', function() { |
1283 .listenTo( this.overlay, 'theme:previous', function() { |
1254 // Renders the previous theme on the overlay |
1284 // Renders the previous theme on the overlay. |
1255 self.previous( [ self.model.cid ] ); |
1285 self.previous( [ self.model.cid ] ); |
1256 }); |
1286 }); |
1257 }, |
1287 }, |
1258 |
1288 |
1259 // This method renders the next theme on the overlay modal |
1289 /* |
1260 // based on the current position in the collection |
1290 * This method renders the next theme on the overlay modal |
1261 // @params [model cid] |
1291 * based on the current position in the collection. |
|
1292 * |
|
1293 * @params [model cid] |
|
1294 */ |
1262 next: function( args ) { |
1295 next: function( args ) { |
1263 var self = this, |
1296 var self = this, |
1264 model, nextModel; |
1297 model, nextModel; |
1265 |
1298 |
1266 // Get the current theme |
1299 // Get the current theme. |
1267 model = self.collection.get( args[0] ); |
1300 model = self.collection.get( args[0] ); |
1268 // Find the next model within the collection |
1301 // Find the next model within the collection. |
1269 nextModel = self.collection.at( self.collection.indexOf( model ) + 1 ); |
1302 nextModel = self.collection.at( self.collection.indexOf( model ) + 1 ); |
1270 |
1303 |
1271 // Sanity check which also serves as a boundary test |
1304 // Sanity check which also serves as a boundary test. |
1272 if ( nextModel !== undefined ) { |
1305 if ( nextModel !== undefined ) { |
1273 |
1306 |
1274 // We have a new theme... |
1307 // We have a new theme... |
1275 // Close the overlay |
1308 // Close the overlay. |
1276 this.overlay.closeOverlay(); |
1309 this.overlay.closeOverlay(); |
1277 |
1310 |
1278 // Trigger a route update for the current model |
1311 // Trigger a route update for the current model. |
1279 self.theme.trigger( 'theme:expand', nextModel.cid ); |
1312 self.theme.trigger( 'theme:expand', nextModel.cid ); |
1280 |
1313 |
1281 } |
1314 } |
1282 }, |
1315 }, |
1283 |
1316 |
1284 // This method renders the previous theme on the overlay modal |
1317 /* |
1285 // based on the current position in the collection |
1318 * This method renders the previous theme on the overlay modal |
1286 // @params [model cid] |
1319 * based on the current position in the collection. |
|
1320 * |
|
1321 * @params [model cid] |
|
1322 */ |
1287 previous: function( args ) { |
1323 previous: function( args ) { |
1288 var self = this, |
1324 var self = this, |
1289 model, previousModel; |
1325 model, previousModel; |
1290 |
1326 |
1291 // Get the current theme |
1327 // Get the current theme. |
1292 model = self.collection.get( args[0] ); |
1328 model = self.collection.get( args[0] ); |
1293 // Find the previous model within the collection |
1329 // Find the previous model within the collection. |
1294 previousModel = self.collection.at( self.collection.indexOf( model ) - 1 ); |
1330 previousModel = self.collection.at( self.collection.indexOf( model ) - 1 ); |
1295 |
1331 |
1296 if ( previousModel !== undefined ) { |
1332 if ( previousModel !== undefined ) { |
1297 |
1333 |
1298 // We have a new theme... |
1334 // We have a new theme... |
1299 // Close the overlay |
1335 // Close the overlay. |
1300 this.overlay.closeOverlay(); |
1336 this.overlay.closeOverlay(); |
1301 |
1337 |
1302 // Trigger a route update for the current model |
1338 // Trigger a route update for the current model. |
1303 self.theme.trigger( 'theme:expand', previousModel.cid ); |
1339 self.theme.trigger( 'theme:expand', previousModel.cid ); |
1304 |
1340 |
1305 } |
1341 } |
1306 }, |
1342 }, |
1307 |
1343 |
1308 // Dispatch audible search results feedback message |
1344 // Dispatch audible search results feedback message. |
1309 announceSearchResults: function( count ) { |
1345 announceSearchResults: function( count ) { |
1310 if ( 0 === count ) { |
1346 if ( 0 === count ) { |
1311 wp.a11y.speak( l10n.noThemesFound ); |
1347 wp.a11y.speak( l10n.noThemesFound ); |
1312 } else { |
1348 } else { |
1313 wp.a11y.speak( l10n.themesFound.replace( '%d', count ) ); |
1349 wp.a11y.speak( l10n.themesFound.replace( '%d', count ) ); |
1564 |
1604 |
1565 $( 'body' ).removeClass( 'show-filters filters-applied show-favorites-form' ); |
1605 $( 'body' ).removeClass( 'show-filters filters-applied show-favorites-form' ); |
1566 $( '.drawer-toggle' ).attr( 'aria-expanded', 'false' ); |
1606 $( '.drawer-toggle' ).attr( 'aria-expanded', 'false' ); |
1567 |
1607 |
1568 // Get the themes by sending Ajax POST request to api.wordpress.org/themes |
1608 // Get the themes by sending Ajax POST request to api.wordpress.org/themes |
1569 // or searching the local cache |
1609 // or searching the local cache. |
1570 this.collection.query( request ); |
1610 this.collection.query( request ); |
1571 |
1611 |
1572 // Set route |
1612 // Set route. |
1573 themes.router.navigate( themes.router.baseUrl( themes.router.searchPath + encodeURIComponent( value ) ), { replace: true } ); |
1613 themes.router.navigate( themes.router.baseUrl( themes.router.searchPath + encodeURIComponent( value ) ), { replace: true } ); |
1574 } |
1614 } |
1575 }); |
1615 }); |
1576 |
1616 |
1577 themes.view.Installer = themes.view.Appearance.extend({ |
1617 themes.view.Installer = themes.view.Appearance.extend({ |
1578 |
1618 |
1579 el: '#wpbody-content .wrap', |
1619 el: '#wpbody-content .wrap', |
1580 |
1620 |
1581 // Register events for sorting and filters in theme-navigation |
1621 // Register events for sorting and filters in theme-navigation. |
1582 events: { |
1622 events: { |
1583 'click .filter-links li > a': 'onSort', |
1623 'click .filter-links li > a': 'onSort', |
1584 'click .theme-filter': 'onFilter', |
1624 'click .theme-filter': 'onFilter', |
1585 'click .drawer-toggle': 'moreFilters', |
1625 'click .drawer-toggle': 'moreFilters', |
1586 'click .filter-drawer .apply-filters': 'applyFilters', |
1626 'click .filter-drawer .apply-filters': 'applyFilters', |
1634 |
1674 |
1635 if ( this.view ) { |
1675 if ( this.view ) { |
1636 this.view.remove(); |
1676 this.view.remove(); |
1637 } |
1677 } |
1638 |
1678 |
1639 // Set ups the view and passes the section argument |
1679 // Sets up the view and passes the section argument. |
1640 this.view = new themes.view.Themes({ |
1680 this.view = new themes.view.Themes({ |
1641 collection: this.collection, |
1681 collection: this.collection, |
1642 parent: this |
1682 parent: this |
1643 }); |
1683 }); |
1644 |
1684 |
1645 // Reset pagination every time the install view handler is run |
1685 // Reset pagination every time the install view handler is run. |
1646 this.page = 0; |
1686 this.page = 0; |
1647 |
1687 |
1648 // Render and append |
1688 // Render and append. |
1649 this.$el.find( '.themes' ).remove(); |
1689 this.$el.find( '.themes' ).remove(); |
1650 this.view.render(); |
1690 this.view.render(); |
1651 this.$el.find( '.theme-browser' ).append( this.view.el ).addClass( 'rendered' ); |
1691 this.$el.find( '.theme-browser' ).append( this.view.el ).addClass( 'rendered' ); |
1652 }, |
1692 }, |
1653 |
1693 |
1654 // Handles all the rendering of the public theme directory |
1694 // Handles all the rendering of the public theme directory. |
1655 browse: function( section ) { |
1695 browse: function( section ) { |
1656 // Create a new collection with the proper theme data |
1696 // Create a new collection with the proper theme data |
1657 // for each section |
1697 // for each section. |
1658 this.collection.query( { browse: section } ); |
1698 this.collection.query( { browse: section } ); |
1659 }, |
1699 }, |
1660 |
1700 |
1661 // Sorting navigation |
1701 // Sorting navigation. |
1662 onSort: function( event ) { |
1702 onSort: function( event ) { |
1663 var $el = $( event.target ), |
1703 var $el = $( event.target ), |
1664 sort = $el.data( 'sort' ); |
1704 sort = $el.data( 'sort' ); |
1665 |
1705 |
1666 event.preventDefault(); |
1706 event.preventDefault(); |
1667 |
1707 |
1668 $( 'body' ).removeClass( 'filters-applied show-filters' ); |
1708 $( 'body' ).removeClass( 'filters-applied show-filters' ); |
1669 $( '.drawer-toggle' ).attr( 'aria-expanded', 'false' ); |
1709 $( '.drawer-toggle' ).attr( 'aria-expanded', 'false' ); |
1670 |
1710 |
1671 // Bail if this is already active |
1711 // Bail if this is already active. |
1672 if ( $el.hasClass( this.activeClass ) ) { |
1712 if ( $el.hasClass( this.activeClass ) ) { |
1673 return; |
1713 return; |
1674 } |
1714 } |
1675 |
1715 |
1676 this.sort( sort ); |
1716 this.sort( sort ); |
1677 |
1717 |
1678 // Trigger a router.naviagte update |
1718 // Trigger a router.navigate update. |
1679 themes.router.navigate( themes.router.baseUrl( themes.router.browsePath + sort ) ); |
1719 themes.router.navigate( themes.router.baseUrl( themes.router.browsePath + sort ) ); |
1680 }, |
1720 }, |
1681 |
1721 |
1682 sort: function( sort ) { |
1722 sort: function( sort ) { |
1683 this.clearSearch(); |
1723 this.clearSearch(); |
1723 if ( ! filter ) { |
1763 if ( ! filter ) { |
1724 return; |
1764 return; |
1725 } |
1765 } |
1726 |
1766 |
1727 // Construct the filter request |
1767 // Construct the filter request |
1728 // using the default values |
1768 // using the default values. |
1729 filter = _.union( [ filter, this.filtersChecked() ] ); |
1769 filter = _.union( [ filter, this.filtersChecked() ] ); |
1730 request = { tag: [ filter ] }; |
1770 request = { tag: [ filter ] }; |
1731 |
1771 |
1732 // Get the themes by sending Ajax POST request to api.wordpress.org/themes |
1772 // Get the themes by sending Ajax POST request to api.wordpress.org/themes |
1733 // or searching the local cache |
1773 // or searching the local cache. |
1734 this.collection.query( request ); |
1774 this.collection.query( request ); |
1735 }, |
1775 }, |
1736 |
1776 |
1737 // Clicking on a checkbox to add another filter to the request |
1777 // Clicking on a checkbox to add another filter to the request. |
1738 addFilter: function() { |
1778 addFilter: function() { |
1739 this.filtersChecked(); |
1779 this.filtersChecked(); |
1740 }, |
1780 }, |
1741 |
1781 |
1742 // Applying filters triggers a tag request |
1782 // Applying filters triggers a tag request. |
1743 applyFilters: function( event ) { |
1783 applyFilters: function( event ) { |
1744 var name, |
1784 var name, |
1745 tags = this.filtersChecked(), |
1785 tags = this.filtersChecked(), |
1746 request = { tag: tags }, |
1786 request = { tag: tags }, |
1747 filteringBy = $( '.filtered-by .tags' ); |
1787 filteringBy = $( '.filtered-by .tags' ); |
1793 _wpnonce: nonce, |
1833 _wpnonce: nonce, |
1794 username: username |
1834 username: username |
1795 }, |
1835 }, |
1796 success: function () { |
1836 success: function () { |
1797 // Get the themes by sending Ajax POST request to api.wordpress.org/themes |
1837 // Get the themes by sending Ajax POST request to api.wordpress.org/themes |
1798 // or searching the local cache |
1838 // or searching the local cache. |
1799 that.collection.query( request ); |
1839 that.collection.query( request ); |
1800 } |
1840 } |
1801 } ); |
1841 } ); |
1802 }, |
1842 }, |
1803 |
1843 |
1804 // Get the checked filters |
1844 /** |
1805 // @return {array} of tags or false |
1845 * Get the checked filters. |
|
1846 * |
|
1847 * @return {Array} of tags or false |
|
1848 */ |
1806 filtersChecked: function() { |
1849 filtersChecked: function() { |
1807 var items = $( '.filter-group' ).find( ':checkbox' ), |
1850 var items = $( '.filter-group' ).find( ':checkbox' ), |
1808 tags = []; |
1851 tags = []; |
1809 |
1852 |
1810 _.each( items.filter( ':checked' ), function( item ) { |
1853 _.each( items.filter( ':checked' ), function( item ) { |
1811 tags.push( $( item ).prop( 'value' ) ); |
1854 tags.push( $( item ).prop( 'value' ) ); |
1812 }); |
1855 }); |
1813 |
1856 |
1814 // When no filters are checked, restore initial state and return |
1857 // When no filters are checked, restore initial state and return. |
1815 if ( tags.length === 0 ) { |
1858 if ( tags.length === 0 ) { |
1816 $( '.filter-drawer .apply-filters' ).find( 'span' ).text( '' ); |
1859 $( '.filter-drawer .apply-filters' ).find( 'span' ).text( '' ); |
1817 $( '.filter-drawer .clear-filters' ).hide(); |
1860 $( '.filter-drawer .clear-filters' ).hide(); |
1818 $( 'body' ).removeClass( 'filters-applied' ); |
1861 $( 'body' ).removeClass( 'filters-applied' ); |
1819 return false; |
1862 return false; |