I am using Sonata Media Bundle with Symfony2.3 in my project. When I override youtube provider sonata.media.provider.youtube. Everything working fine. No errors or anything but in
/Web/uploads/media
no thumbnails generated but in my admin dashboard show me :
/uploads/media/default/0001/01/thumb_9_default_small.jpg.
I don't know how but when I am not override sonata.media.provider.youtube then thumbnails generated in
/web/uploads/media/
Like :
/web/uploads/media/default/0001/01/thumb_9_default_small.jpg.
I don't know why ?
Here is my config file where I create custom service provider:
services:
sonata.media.provider.youtubecustom:
class: %application_sonata_media.youtubecustom_class%
tags:
- { name: sonata.media.provider }
arguments:
- sonata.media.provider.youtubecustom
- #sonata.media.filesystem.local
- #sonata.media.cdn.server
- #sonata.media.generator.default
- #sonata.media.thumbnail.format
- #sonata.media.buzz.browser
- #sonata.media.metadata.proxy
calls:
- [ setTemplates, [ { helper_thumbnail: SonataMediaBundle:Provider:thumbnail.html.twig,helper_view: SonataMediaBundle:Provider:view_youtube.html.twig } ] ]
parameters:
application_sonata_media.youtubecustom_class: Application\Sonata\MediaBundle\Provider\YoutubeCustomProvider
And SonataMedia.yml :
sonata_media:
db_driver: doctrine_orm # | doctrine_mongodb
default_context: default
contexts:
default: # the default context is mandatory
download:
mode: http # X-Sendfile | http
providers:
- sonata.media.provider.youtubecustom
#- sonata.media.provider.youtube
- sonata.media.provider.image
#- sonata.media.provider.file
#- sonata.media.provider.vimeo
formats:
small: { width: 100, quality: 100}
big: { width: 820 , quality: 100}
news:
providers:
- sonata.media.provider.image
formats:
abtract: { width: 100, quality: 100}
wide: { width: 820 , quality: 100}
cdn:
server:
path: /uploads/media # http://media.sonata-project.org/
filesystem:
# define where the uploaded file will be stored
local:
directory: %kernel.root_dir%/../web/uploads/media
create: true
providers:
file:
resizer: false
pixlr:
enabled: true
referrer: Demo - Sonata Project
resizer:
simple:
#mode: outbound
mode: inset
# Enable Doctrine to map the provided entities
doctrine:
orm:
entity_managers:
default:
mappings:
ApplicationSonataMediaBundle: ~
#SonataMediaBundle: ~
My custom youtube provider file :
<?php
namespace Application\Sonata\MediaBundle\Provider;
use Sonata\MediaBundle\Model\MediaInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Gaufrette\Filesystem;
use Sonata\MediaBundle\CDN\CDNInterface;
use Sonata\MediaBundle\Generator\GeneratorInterface;
use Buzz\Browser;
use Sonata\MediaBundle\Thumbnail\ThumbnailInterface;
use Sonata\MediaBundle\Metadata\MetadataBuilderInterface;
use Sonata\MediaBundle\Provider\YouTubeProvider;
class YoutubeCustomProvider extends YouTubeProvider
{
/* #var boolean */
protected $html5;
/**
* #param string $name
* #param \Gaufrette\Filesystem $filesystem
* #param \Sonata\MediaBundle\CDN\CDNInterface $cdn
* #param \Sonata\MediaBundle\Generator\GeneratorInterface $pathGenerator
* #param \Sonata\MediaBundle\Thumbnail\ThumbnailInterface $thumbnail
* #param \Buzz\Browser $browser
* #param \Sonata\MediaBundle\Metadata\MetadataBuilderInterface $metadata
* #param boolean $html5
*/
public function __construct($name, Filesystem $filesystem, CDNInterface $cdn, GeneratorInterface $pathGenerator, ThumbnailInterface $thumbnail, Browser $browser, MetadataBuilderInterface $metadata = null, $html5 = false)
{
parent::__construct($name, $filesystem, $cdn, $pathGenerator, $thumbnail, $browser, $metadata);
$this->html5 = $html5;
}
/**
* {#inheritdoc}
*/
public function getHelperProperties(MediaInterface $media, $format, $options = array())
{
// Override html5 value if $options['html5'] is a boolean
if (!isset($options['html5'])) {
$options['html5'] = $this->html5;
}
// documentation : http://code.google.com/apis/youtube/player_parameters.html
$default_player_url_parameters = array(
//Values: 0 or 1. Default is 1. Sets whether the player should load related
// videos once playback of the initial video starts. Related videos are
// displayed in the "genie menu" when the menu button is pressed. The player
// search functionality will be disabled if rel is set to 0.
'rel' => 0,
// Values: 0 or 1. Default is 0. Sets whether or not the initial video will autoplay
// when the player loads.
'autoplay' => 0,
// Values: 0 or 1. Default is 0. In the case of a single video player, a setting of 1
// will cause the player to play the initial video again and again. In the case of a
// playlist player (or custom player), the player will play the entire playlist and
// then start again at the first video.
'loop' => 0,
// Values: 0 or 1. Default is 0. Setting this to 1 will enable the Javascript API.
// For more information on the Javascript API and how to use it, see the JavaScript
// API documentation.
'enablejsapi' => 0,
// Value can be any alphanumeric string. This setting is used in conjunction with the
// JavaScript API. See the JavaScript API documentation for details.
'playerapiid' => null,
// Values: 0 or 1. Default is 0. Setting to 1 will disable the player keyboard controls.
// Keyboard controls are as follows:
// Spacebar: Play / Pause
// Arrow Left: Jump back 10% in the current video
// Arrow Right: Jump ahead 10% in the current video
// Arrow Up: Volume up
// Arrow Down: Volume Down
'disablekb' => 0,
// Values: 0 or 1. Default is 0. Setting to 1 enables the "Enhanced Genie Menu". This
// behavior causes the genie menu (if present) to appear when the user's mouse enters
// the video display area, as opposed to only appearing when the menu button is pressed.
'egm' => 0,
// Values: 0 or 1. Default is 0. Setting to 1 enables a border around the entire video
// player. The border's primary color can be set via the color1 parameter, and a
// secondary color can be set by the color2 parameter.
'border' => 0,
// Values: Any RGB value in hexadecimal format. color1 is the primary border color, and
// color2 is the video control bar background color and secondary border color.
'color1' => null,
'color2' => null,
// Values: 0 or 1. Default is 0. Setting to 1 enables the fullscreen button. This has no
// effect on the Chromeless Player. Note that you must include some extra arguments to
// your embed code for this to work.
'fs' => 1,
// Values: A positive integer. This parameter causes the player to begin playing the video
// at the given number of seconds from the start of the video. Note that similar to the
// seekTo function, the player will look for the closest keyframe to the time you specify.
// This means sometimes the play head may seek to just before the requested time, usually
// no more than ~2 seconds
'start' => 0,
// Values: 0 or 1. Default is 0. Setting to 1 enables HD playback by default. This has no
// effect on the Chromeless Player. This also has no effect if an HD version of the video
// is not available. If you enable this option, keep in mind that users with a slower
// connection may have an sub-optimal experience unless they turn off HD. You should ensure
// your player is large enough to display the video in its native resolution.
'hd' => 1,
// Values: 0 or 1. Default is 1. Setting to 0 disables the search box from displaying when
// the video is minimized. Note that if the rel parameter is set to 0 then the search box
// will also be disabled, regardless of the value of showsearch.
'showsearch' => 0,
// Values: 0 or 1. Default is 1. Setting to 0 causes the player to not display information
// like the video title and rating before the video starts playing.
'showinfo' => 0,
// Values: 1 or 3. Default is 1. Setting to 1 will cause video annotations to be shown by
// default, whereas setting to 3 will cause video annotation to not be shown by default.
'iv_load_policy' => 1,
// Values: 1. Default is based on user preference. Setting to 1 will cause closed captions
// to be shown by default, even if the user has turned captions off.
'cc_load_policy' => 1,
// Values: 'window' or 'opaque' or 'transparent'.
// When wmode=window, the Flash movie is not rendered in the page.
// When wmode=opaque, the Flash movie is rendered as part of the page.
// When wmode=transparent, the Flash movie is rendered as part of the page.
'wmode' => 'window'
);
$default_player_parameters = array(
// Values: 0 or 1. Default is 0. Setting to 1 enables a border around the entire video
// player. The border's primary color can be set via the color1 parameter, and a
// secondary color can be set by the color2 parameter.
'border' => $default_player_url_parameters['border'],
// Values: 'allowfullscreen' or empty. Default is 'allowfullscreen'. Setting to empty value disables
// the fullscreen button.
'allowFullScreen' => $default_player_url_parameters['fs'] == '1' ? true : false,
// The allowScriptAccess parameter in the code is needed to allow the player SWF to call
// functions on the containing HTML page, since the player is hosted on a different domain
// from the HTML page.
'allowScriptAccess' => isset($options['allowScriptAccess']) ? $options['allowScriptAccess'] : 'always',
// Values: 'window' or 'opaque' or 'transparent'.
// When wmode=window, the Flash movie is not rendered in the page.
// When wmode=opaque, the Flash movie is rendered as part of the page.
// When wmode=transparent, the Flash movie is rendered as part of the page.
'wmode' => $default_player_url_parameters['wmode']
);
$player_url_parameters = array_merge($default_player_url_parameters, isset($options['player_url_parameters']) ? $options['player_url_parameters'] : array());
$box = $this->getBoxHelperProperties($media, $format, $options);
$player_parameters = array_merge($default_player_parameters, isset($options['player_parameters']) ? $options['player_parameters'] : array(), array(
'width' => $box->getWidth(),
'height' => $box->getHeight()
));
$params = array(
'html5' => $options['html5'],
'player_url_parameters' => http_build_query($player_url_parameters),
'player_parameters' => $player_parameters
);
return $params;
}
/**
* {#inheritdoc}
*/
protected function fixBinaryContent(MediaInterface $media)
{
if (!$media->getBinaryContent()) {
return;
}
if (preg_match("/(?<=v(\=|\/))([-a-zA-Z0-9_]+)|(?<=youtu\.be\/)([-a-zA-Z0-9_]+)/", $media->getBinaryContent(), $matches)) {
$media->setBinaryContent($matches[2]);
}
}
/**
* {#inheritdoc}
*/
protected function doTransform(MediaInterface $media)
{
$this->fixBinaryContent($media);
if (!$media->getBinaryContent()) {
return;
}
$media->setProviderName($this->name);
$media->setProviderStatus(MediaInterface::STATUS_OK);
$media->setProviderReference($media->getBinaryContent());
$this->updateMetadata($media, true);
}
/**
* {#inheritdoc}
*/
public function updateMetadata(MediaInterface $media, $force = false)
{
$url = sprintf('http://www.youtube.com/oembed?url=http://www.youtube.com/watch?v=%s&format=json', $media->getProviderReference());
try {
$metadata = $this->getMetadata($media, $url);
} catch (\RuntimeException $e) {
$media->setEnabled(false);
$media->setProviderStatus(MediaInterface::STATUS_ERROR);
return;
}
$media->setProviderMetadata($metadata);
if ($force) {
$media->setName($metadata['title']);
$media->setAuthorName($metadata['author_name']);
}
$media->setHeight($metadata['height']);
$media->setWidth($metadata['width']);
$media->setContentType('video/x-flv');
}
/**
* {#inheritdoc}
*/
public function getDownloadResponse(MediaInterface $media, $format, $mode, array $headers = array())
{
return new RedirectResponse(sprintf('http://www.youtube.com/watch?v=%s', $media->getProviderReference()), 302, $headers);
}
}
And default youtube service created by Sonata Media Bundle which one I am override :
<service id="sonata.media.provider.youtube" class="%sonata.media.provider.youtube.class%">
<tag name="sonata.media.provider" />
<argument>sonata.media.provider.youtube</argument>
<argument />
<argument />
<argument />
<argument type="service" id="sonata.media.thumbnail.format" />
<argument type="service" id="sonata.media.buzz.browser" />
<argument type="service" id="sonata.media.metadata.proxy" />
<argument />
<call method="setTemplates">
<argument type="collection">
<argument key='helper_thumbnail'>SonataMediaBundle:Provider:thumbnail.html.twig</argument>
<argument key='helper_view'>SonataMediaBundle:Provider:view_youtube.html.twig</argument>
</argument>
</call>
</service>
My Service provider is the same as default youtube provider.
Same service name and provider name write in our bundle then problem solved.
Like if you extend YoutubeProvider then same provider name write in your bundle.
See this:
My Config.yml
services:
sonata.media.provider.youtube:
class: %application_sonata_media.youtube_class%
tags:
- { name: sonata.media.provider }
arguments:
- sonata.media.provider.youtube
- #sonata.media.filesystem.local
- #sonata.media.cdn.server
- #sonata.media.generator.default
- #sonata.media.thumbnail.format
- #sonata.media.buzz.browser
- #sonata.media.metadata.proxy
calls:
- [ setTemplates, [ { helper_thumbnail: SonataMediaBundle:Provider:thumbnail.html.twig,helper_view: SonataMediaBundle:Provider:view_youtube.html.twig } ] ]
parameters:
application_sonata_media.youtube_class: Application\Sonata\MediaBundle\Provider\YoutubeProvider
My YoutubeProvider.php
<?php
namespace Application\Sonata\MediaBundle\Provider;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Gaufrette\Filesystem;
use Sonata\MediaBundle\CDN\CDNInterface;
use Sonata\MediaBundle\Generator\GeneratorInterface;
use Buzz\Browser;
use Sonata\MediaBundle\Thumbnail\ThumbnailInterface;
use Sonata\MediaBundle\Metadata\MetadataBuilderInterface;
use Sonata\MediaBundle\Provider\YouTubeProvider as MainYouTubeProvider;
use Sonata\AdminBundle\Form\FormMapper;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull;
use Sonata\MediaBundle\Model\MediaInterface;
class YoutubeProvider extends MainYouTubeProvider
{
//write provider functions
}
It is not the best way to redefine vendors services entirely - this may not work.
There are some common practices in this case (http://symfony.com/doc/current/cookbook/bundles/override.html#services-configuration).
If you want just to redefine the providers class and not change service definition, you only have to change value of parameter sonata.media.provider.youtube.class.
If you want to redefine some part of vendors service (calls, tags etc.) better use CompilerPass.
namespace Acme\DemoBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class OverrideServiceCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('sonata.media.provider.youtube');
$definition->addTag($name, $attributes);
}
}
Related
I would like to make responsive flat panel - on a mobile device should be in full screen like there
https://tour.croptrust.org/
Is there a possibility to achieve that in react-360.
Here is my example https://gstuczynski.com/tour/plaszow/ - try to hit loupe -> on the desktop looks good but how to make it responsive?
Here is my code
https://github.com/gstuczynski/plaszow-cc-virtual-tour
If you're looking to set your surface size based on whether user is on mobile or desktop, I'd approach this by building a NativeModule to check if the device is mobile on componentDidMount() and using the built in resize() function to adjust your surface sizes.
First add a module in client.js
function init(bundle, parent, options = {}) {
const r360 = new ReactInstance(bundle, parent, {
nativeModules: [
//new module added here
ctx => new SurfaceModule(ctx),
],
fullScreen: true,
...options,
});
}
In your case you already have vars for the surfaces - but remove const to expose them to your NativeModule:
infoPanel = new Surface(1440, 850, Surface.SurfaceShape.Flat);
mapPanel = new Surface(850, 800, Surface.SurfaceShape.Flat);
cylinderSurface = new Surface(4096, 720, Surface.SurfaceShape.Cylinder);
Next, create your class SurfaceModule and include a function to check for mobile and include a ref for the surface namecheckMobile(surfaceName)
import {Module} from 'react-360-web';
export default class SurfaceModule extends Module {
constructor(ctx) {
super('SurfaceModule');
this._ctx = ctx;
}
checkMobile(surfaceName, id) {
if (navigator.userAgent.match('add your match criteria here'))
{
let swidth = screen.width
let sheight = screen.height
surfaceName.resize(swidth, sheight);
this._ctx.invokeCallback(
id,
[swidth, sheight]
);
}
}
}
Finally in index.js run the function to check and adjust surface sizes.
With this setup you'll need to call the function for each Surface you want to check or you could rework to send multiple refs and perform an if/switch
You could also move the call out of componentDidMount() into another func
import {
NativeModules
} from 'react-360';
const SurfaceModule = NativeModules.SurfaceModule;
componentDidMount() {
SurfaceModule.checkMobile((swidth, sheight) => {
console.log(swidth);
console.log(sheight);
});
}
EDIT: Updated checkMobile() func & index.js to include callback
I have not come across another js plugin for a lucky spinning wheel better than this one yet. I'm having an issue with the getIndicatedSegment property when calling the alert prize(); function in an Ionic 3/Angular 2 application. I've followed this issue how to use winwheel.js callback in angular2 and have gotten it to work with the app and spinning quite well.
When the alert prize function is called, I get this error:
TypeError: Cannot read property 'getIndicatedSegment' of undefined
at alertPrize (http://localhost:8100/?ionicplatform=ios:51:41)
at eval (eval at winwheelStopAnimation
I've followed the advice in that answer and added the prize function in my index.html file in order to get access to the Prize callback that is in my page.ts file and show the alert on the basic example to alert the prize. However, when I am trying to access the getIndicatedSegment variable, I get this issue.
Here is the code in my LuckySpinPage.ts file:
export class LuckySpinPage {
constructor(public navCtrl: NavController) { }
wheel;
wheelSpinning = false;
ngAfterViewInit() {
this.initWheel();
}
initWheel() {
this.wheel = new Winwheel({
'numSegments': 10, // Specify number of segments.
'outerRadius': 150, // Set radius to so wheel fits the background.
'innerRadius': 30, // Set inner radius to make wheel hollow.
'pointerAngle': 0,
'pointerGuide': false, // Turn pointer guide on.
'drawMode' : 'segmentImage',
'segments': [
{'image' : '../../assets/images/segment-winner.png'},
{'image' : '../../assets/images/segment-1.png'},
{'image' : '../../assets/images/segment-2.png'},
{'image' : '../../assets/images/segment-3.png'},
{'image' : '../../assets/images/segment-5.png'},
{'image' : '../../assets/images/segment-6.png'},
{'image' : '../../assets/images/segment-7.png'},
{'image' : '../../assets/images/segment-8.png'},
{'image' : '../../assets/images/segment-9.png'},
{'image' : '../../assets/images/segment-10.png'}
],
'animation': // Define spin to stop animation.
{
'type': 'spinToStop',
'duration': 5,
'spins': 10,
'callbackFinished': 'alertPrize()'
}
});
}
// -------------------------------------------------------
// Click handler for spin button.
// -------------------------------------------------------
startSpin() {
// Ensure that spinning can't be clicked again while already running.
if (this.wheelSpinning === false) {
this.wheel.startAnimation();
this.wheelSpinning = true;
}
}
}
The code in the index.html file:
<script>
// This function called after the spin animation has stopped.
function alertPrize(){
// Call getIndicatedSegment() function to return pointer to the segment
pointed to on wheel.
var winningSegment = this.wheel.getIndicatedSegment();
// Basic alert of the segment text which is the prize name.
alert("You have won " + winningSegment.text + "!");
}
</script>
My aim is to get the prize on a specific segment e.g alert you've won, when it lands on segment 4, or alert not prize won when landing on any other segment.
The question: In Firefox WebExtensions, from arbitrary background origins, how can I open an arbitrary page in a tab, uniquely?
Requirements:
1) Arbitrary background origins. My initial use case is from browser_action contextMenus. However, the technique ought to work from popup, options, or any custom background script. For the moment, not concerned with content pages. Although if the technique works for them too, great.
2) Open an arbitrary page in a tab. My initial use is for the options page, but the technique should work for arbitrary background pages (options, popup, or custom pages).
3) Open uniquely. If it's the first time, open in a new tab. If the tab was previously opened, focus to the existing tab, don't create duplicate tabs.
4) If I close a tab, I need to make sure I remove my reference to the previously opened tab ID.
I've answered my own question with one possible solution.
If the solution could be improved upon, let me know. Thank you.
If the question is a duplicate, I'll remove and post solution elsewhere if appropriate.
This is an attempt to answer my own question, with comments inline.
This simple example assumes all pagers live in the top level directory, but this is arbitrary and easily changed.
Essentially it consists of four parts:
1) A global object tabIDs to hold the page names (without '.html'). You could change this to be full path including extension, or keep the page name as a short name and modify technique to use another option like path for the full path name, etc.
2) An async function (to make use of the new await feature) named tabCreate to determine if one is already open and switch to it or create a new tab.
3) An onRemoved event handler, tabRemove to handle cleanup of tabIDs after a tab is closed (if it was one of interest).
4) A usage example, from a context menu item, passing some a page and a panel option, which have no use in this question, but are part of another question, and just here for demonstration purposes.
background.js:
// tabID contexts
// global var to keep track of different tabs,
// i.e. options.html, popup.html and so on.
var tabIDs = {
'options': null,
'popup': null,
}
// Requires Firefox 52.0+ to use async/await
// opts correspond to contexts above in tabIDs
// of the form { 'page': 'options' } or { 'page': 'popup' }
// note if using Node.js, this may require v7+ and --harmony_async_await
async function tabCreate ( opts ) {
var tab;
if ( tabIDs[ opts.page ] !== null ) {
// should probably bring window to front first... oops
// ..
// switch to tab
tab = await browser.tabs.update( tabIDs[ opts.page ], { active: true } );
} else {
tab = await browser.tabs.create( {
'url': opts.page + '.html'
} );
tabIDs[ opts.page ] = tab.id;
}
console.log( '**** opts.page = ' + opts.page + ', opts.tab = ' + opts.tab + ', tab.id = ' + tab.id );
}
// When tabs are closed, see if the tabID is in tabIDs,
// and if so, set it to null
function tabRemove ( tabID, removeInfo ) {
console.log( 'Closed TAB ' + tabID );
Object.keys( tabIDs ).forEach( function( key, index ) {
if ( tabIDs[ key ] === tabID ) {
tabIDs[ key ] = null;
return;
}
} );
}
browser.tabs.onRemoved.addListener( tabRemove );
/*
* Context Menus
*/
browser.contextMenus.removeAll( );
browser.contextMenus.create( {
title: 'My Web Extension',
contexts: [ 'browser_action' ],
} );
browser.contextMenus.create( {
title: 'Options',
contexts: [ 'browser_action' ],
onclick: myWebExt_Options
} );
function myWebExt_Options ( ) {
tabCreate( {
'page': 'options',
'panel': 1,
} );
}
Another approach might be to add an event listener to each page, and when closed, send a message back to background.js, but that seems much more complicated with little or no benefit.
I've some ExtJS 4 MVC apps that share certain custom view components. A shared component has its own namespace and is often sufficiently complex (e.g. with a large tree of descendant components that must be managed) that it warrants its own controller. I want this 'component specific controller' to be properly encapsulated (private) within the component (i.e. nothing outside the component should have to know or care that the component is using an embedded controller). Of course, it should be possible for an app to have multiple instances of the component (each encapsulating a separate instance of its component controller).
To this end, I've developed the following mixin (this version for ExtJS 4.1), but I'm keen to know if anyone has solved the same problem more elegantly:
/**
* A mixin that provides 'controller' style facilities to view components.
*
* This is for 'view' components that also serve as the 'controller' for the component.
* In such situations where an embedded controller is required we would have preferred to use a separate
* Ext.app.Controller derived controller but it looks like Ext.app.Controllers are global things,
* i.e. their refs can't be scoped to within the component (allowing multiple instances on the component to be used,
* each with their own controller each with its own set of refs).
*
* Usage:
* - Declare a 'refs' config just as in an Ext.app.Controller.
* - Call this.setupControllerRefs() from your initComponent() template function.
* - Call this.control(String/Object selectors, Object listeners) just as you would in a Ext.app.Controller.init()
* template function.
* - Any events fired from within a Window need special treatment, because Ext creates Windows as floated
* (top-level) components, so our usual selector scoping technique doesn't work. The trick is to give each Window
* an itemId prefixed with this component's itemId, e.g. itemId: me.itemId + '-lookup-window'
* Then, in the 'this.control({...})' block, define selectors as necessary that begin with "#{thisItemId}-", e.g.
* '#{thisItemId}-lookup-window aux-filter-criteria': ...
*
* It is also recommended to keep the 'view' aspect of the component minimal. If there is a significant proportion of
* view code, push it down into a new component class. Ideally, the component/controller should be just a Container.
*/
Ext.define('Acme.CmpController', {
setupControllerRefs: function() {
var me = this,
refs = me.refs;
// Copied from Ext.app.Controller.ref
refs = Ext.Array.from(refs);
Ext.Array.each(refs, function(info) {
var ref = info.ref,
fn = 'get' + Ext.String.capitalize(ref);
if (!me[fn]) {
me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
}
});
},
/** #private (copied from Ext.app.Controller.ref) */
getRef: function(ref, info, config) {
this.refCache = this.refCache || {};
info = info || {};
config = config || {};
Ext.apply(info, config);
if (info.forceCreate) {
return Ext.ComponentManager.create(info, 'component');
}
var me = this,
selector = info.selector,
cached = me.refCache[ref];
if (!cached) {
//me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
/**** ACME ****/ me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector, this)[0];
if (!cached && info.autoCreate) {
me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
}
if (cached) {
cached.on('beforedestroy', function() {
me.refCache[ref] = null;
});
}
}
return cached;
},
control: function(selectors, listeners) {
var me = this,
selectorPrefix,
thisIemIdPrefix = '#{thisItemId}',
newSelectors = {};
if (listeners)
throw "Support for the optional 'listeners' param (which we had thought was rarely used) has not yet been coded.";
// Since there could be multiple instances of the controller/component, each selector needs to be
// prefixed with something that scopes the query to within this component. Ensure each instance has
// an itemId, and use this as the basis for scoped selectors.
me.itemId = me.itemId || me.id;
if (!me.itemId)
throw "We assume the component will always have an 'id' by the time control() is called.";
selectorPrefix = '#' + me.itemId + ' ';
Ext.Object.each(selectors, function(selector, listeners) {
if (selector.indexOf(thisIemIdPrefix) === 0)
selector = '#' + me.itemId + selector.substring(thisIemIdPrefix.length);
else
selector = selectorPrefix + selector;
newSelectors[selector] = listeners;
});
selectors = newSelectors;
// Real Controllers use the EventBus, so let's do likewise.
// Note: this depends on a hacked EventBus ctor. See ext-fixes.js
Ext.app.EventBus.theInstance.control(selectors, listeners, me);
}
});
As referred to in that last comment, ExtJS must be patched as follows:
Ext.override(Ext.app.EventBus, {
/**
* Our CmpController mixin needs to get a handle on the EventBus, as created by the Ext.app.Application instance. Analysis
* of the ExtJS source code shows that only one instance of EventBus gets created (assuming there's never more than one
* Ext.app.Application per app). So we hack the ctor to store a reference to itself as a static 'theInstance' property.
*/
constructor: function() {
this.callOverridden();
/**** ACME ****/ this.self.theInstance = this;
},
/**
* Had to patch this routine on the line labelled **** ACME ****. Events intercepted by Pv were being received by the Pv
* instance first created by appPv. appPv created a new Pv instance every time a 'to.AcmeViewer.View' message is received.
* Even though the old Pv had isDestroyed:true, the routine below was dispatching the event to it.
*
* It's possible this surprising behaviour is not unconnected with our (mis?)use of EventBus in Acme.CmpController.
*
* This patched function is from ExtJS 4.1.1
*/
dispatch: function(ev, target, args) {
var bus = this.bus,
selectors = bus[ev],
selector, controllers, id, events, event, i, ln;
if (selectors) {
// Loop over all the selectors that are bound to this event
for (selector in selectors) {
// Check if the target matches the selector
if (selectors.hasOwnProperty(selector) && target.is(selector)) {
// Loop over all the controllers that are bound to this selector
controllers = selectors[selector];
for (id in controllers) {
if (controllers.hasOwnProperty(id)) {
// Loop over all the events that are bound to this selector on this controller
events = controllers[id];
for (i = 0, ln = events.length; i < ln; i++) {
event = events[i];
/**** ACME ****/ if (!event.observable.isDestroyed)
// Fire the event!
if (event.fire.apply(event, Array.prototype.slice.call(args, 1)) === false) {
return false;
}
}
}
}
}
}
}
return true;
}
});
Although I'm currently on ExtJS 4.1 I'm also interested to hear about solutions that depend on 4.2, as this may help motivate me to migrate.
I've been trying to get mobile_tools for Drupal 7 to work but it appears it's not switching themes. (Or at least I can't get it to switch).
This is the code "in charge of changing to the mobile theme. If I print $custom_theme it does become the name of the mobile theme but the displayed theme is still the default. I have not been able to find any documentation on variable $custom_theme at least not for Drupal 7.
/**
* Being called in the hook_boot() implementation
* This function is in charge of changing to the mobile theme
*/
function mobile_tools_switch_theme($device) {
global $custom_theme, $conf;
// check if theme switching is forced
$current_url_type = mobile_tools_is_mobile_site();
if (($current_url_type == 'mobile' && variable_get('mobile-tools-theme-switch', '' ) == 'mobile-tools-mobile-url') ||
(variable_get('mobile-tools-theme-switch', '' ) == 'mobile-tools-mobile-device' && $device['type'] == 'mobile') ) {
$group = $device['group'];
$mobile_detection_module = variable_get('mobile-tools-device-detection', 'mobile_tools');
if (variable_get($mobile_detection_module . '_' . $group . '_enable', '') == 1) {
$custom_theme = variable_get($mobile_detection_module . '_' . $group . '_theme', $conf['theme_default']);
return TRUE;
}
else {
$custom_theme = variable_get('mobile_tools_theme_name', $conf['theme_default']);
return TRUE;
}
}
return FALSE;
}
$custom_theme is no longuer the way to switch themes in Drupal 7 as mentionned in the Upgrade modules from 6.x to 7.x page, you have to use hook_custom_theme or specify a theme callback for the path instead.
Example from the upgrade page:
<?php
/**
* Implements hook_menu_alter().
*/
function mymodule_menu_alter(&$items) {
// Set the theme callback function for all node pages. As per the
// standard behavior for hook_menu() properties, this will be
// inherited by all paths underneath node/%node as well, unless
// they define their own theme callback.
$items['node/%node']['theme callback'] = 'mymodule_default_node_theme';
// Set a different theme callback for node edit pages, and pass
// along the node object to this function so we can make decisions
// based on it.
$items['node/%node/edit']['theme callback'] = 'mymodule_edit_node_theme';
$items['node/%node/edit']['theme arguments'] = array(1);
}
/**
* Defaults to using the 'some_theme' theme for node pages.
*/
function mymodule_default_node_theme() {
return 'some_theme';
}
/**
* For editing page nodes, uses the 'some_other_theme' theme.
*/
function mymodule_edit_node_theme($node) {
return $node->type == 'page' ? 'some_other_theme' : mymodule_default_node_theme();
}
/**
* Implements hook_custom_theme().
*/
function mymodule_custom_theme() {
global $user;
// If the current user has a special role assigned to them, then display all
// pages of the site (including those listed above) using the 'special_theme'
// theme.
if (in_array(variable_get('mymodule_special_role', 0), array_keys($user->roles))) {
return 'special_theme';
}
}
?>