In jQuery I can use live() to add event listeners, even for elements that don't exist yet:
jQuery('a[href*="/item/"]', pageContent).live('click', preLoadAjaxPage);
Does Closure Library have an equivalent?
goog.events.EventType doesn't have any "DOM change" event, so I can not do goog.events.listen(goog.dom.getDocument(), goog.events.EventType.DOM_CHANGE, addEventListenersAgain) or similar.
I followed #Felix Kling's suggestion and registered the event handler at root:
goog.events.listen(document.body, goog.events.EventType.CLICK,
/**
* #param {goog.events.BrowserEvent} event
*/
function(event) {
var realEvent = event.event_;
var el = /** #type {HTMLAnchorElement} */ (event).target;
if (el.tagName.toLowerCase() == 'a' && (href matches pattern)) {
// ...
}
});
Related
I have a reporting page that is basically a table you can add and remove columns from. When you add a column, the data for that column is fetched and loaded with ajax, using angular.
Consider this Behat scenario:
Given I have a user named "Dillinger Four"
And I am on "/reports"
When I add the "User's Name" column
Then I should see "Dillinger Four"
How can I make Behat wait until angular's ajax call completes? I would like to avoid using a sleep, since sleeps add unnecessary delay and will fail if the call takes too long.
I used the following to wait for jquery code:
$this->getSession()->wait($duration, '(0 === jQuery.active)');
I haven't found a similar value to check with angular.
Your link above was helpful, just to expand on it and save someone else a little time.
/**
* #Then /^I should see "([^"]*)" if I wait "([^"]*)"$/
*/
public function iShouldSeeIfIWait($text, $time)
{
$this->spin(function($context) use ($text) {
$this->assertPageContainsText($text);
return true;
}, intval($time) );
}
/**
* Special function to wait until angular has rendered the page fully, it will keep trying until either
* the condition is meet or the time runs out.
*
* #param function $lambda A anonymous function
* #param integer $wait Wait this length of time
*/
public function spin ($lambda, $wait = 60)
{
for ($i = 0; $i < $wait; $i++)
{
try {
if ($lambda($this)) {
return true;
}
} catch (Exception $e) {
// do nothing
}
sleep(1);
}
$backtrace = debug_backtrace();
throw new Exception(
"Timeout thrown by " . $backtrace[1]['class'] . "::" . $backtrace[1]['function'] . "()\n" .
$backtrace[1]['file'] . ", line " . $backtrace[1]['line']
);
}
Then in your Scenario use:
Then I should see "Something on the page." if I wait "5"
You can use code from Angular's Protractor library to wait for loading. Here you can find a function waitForAngular(). It simply waits for a client-side function with the same name
Here's working PHP code.
class WebContext implements Context
{
/**
* #Then the list of products should be:
*/
public function theListOfProductsShouldBe(TableNode $table)
{
$this->waitForAngular();
// ...
}
private function waitForAngular()
{
// Wait for angular to load
$this->getSession()->wait(1000, "typeof angular != 'undefined'");
// Wait for angular to be testable
$this->getPage()->evaluateScript(
'angular.getTestability(document.body).whenStable(function() {
window.__testable = true;
})'
);
$this->getSession()->wait(1000, 'window.__testable == true');
}
}
I am constructing a tree using the Google Closure Library. Now I want the nodes to expand on a single mouseclick, but I seem not to get it working.
I've tried calling goog.ui.component.EventType.SELECT, but it won't work.
At my tree-component class I've added the following event:
goog.events.listen(item, [goog.ui.Component.EventType.SELECT, goog.ui.tree.BaseNode.EventType.EXPAND], this.dispatchEvent, false, this);
And at my class calling the function i've added:
goog.events.listen(this._tree, [goog.ui.Component.EventType.SELECT, goog.ui.tree.BaseNode.EventType.EXPAND], this.treeClick, false, this)
Any suggestions on how I could expand my node with a single click?
I see that BaseNode is screwing up any click events:
/**
* Handles a click event.
* #param {!goog.events.BrowserEvent} e The browser event.
* #protected
* #suppress {underscore}
*/
goog.ui.tree.BaseNode.prototype.onClick_ = goog.events.Event.preventDefault;
When adding this code to the goog/demos/tree/demo.html:
goog.ui.tree.BaseNode.prototype.onClick_ = function (e) {
var qq = this.expanded_?this.collapse():this.expand();
};
The tree control expands and collapses on one click.
Tried to extend goog.ui.tree.TreeControl and override createNode to return a custom goog.ui.tree.TreeNode that overrides onClick but could not do it without getting errors. In theory you could create your custom TreeControl that expands and collapses on one click.
If you want to support non collapsable content and other features I guess you have to trigger some sort of event instead of just extend or callapse the TreeNode.
[update]
After some fiddling I got it working by subclassing TreeControl and TreeNode added the following code to goog/demos/tree/demo.html
/**
* This creates a myTreeControl object. A tree control provides a way to
* view a hierarchical set of data.
* #param {string} html The HTML content of the node label.
* #param {Object=} opt_config The configuration for the tree. See
* goog.ui.tree.TreeControl.defaultConfig. If not specified, a default config
* will be used.
* #param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
* #constructor
* #extends {goog.ui.tree.BaseNode}
*/
var myTreeControl = function (html, opt_config, opt_domHelper) {
goog.ui.tree.TreeControl.call(this, html, opt_config, opt_domHelper);
};
goog.inherits(myTreeControl, goog.ui.tree.TreeControl);
/**
* Creates a new myTreeNode using the same config as the root.
* myTreeNode responds on single clicks
* #param {string} html The html content of the node label.
* #return {goog.ui.tree.TreeNode} The new item.
* #override
*/
myTreeControl.prototype.createNode = function (html) {
return new myTreeNode(html || '', this.getConfig(),
this.getDomHelper());
};
/**
* A single node in the myTreeControl.
* #param {string} html The html content of the node label.
* #param {Object=} opt_config The configuration for the tree. See
* goog.ui.tree.TreeControl.defaultConfig. If not specified, a default config
* will be used.
* #param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
* #constructor
* #extends {goog.ui.tree.BaseNode}
*/
var myTreeNode = function (html, opt_config, opt_domHelper) {
goog.ui.tree.TreeNode.call(this,html, opt_config, opt_domHelper)
}
goog.inherits(myTreeNode, goog.ui.tree.TreeNode);
/**
* Handles a click event.
* #param {!goog.events.BrowserEvent} e The browser event.
* #override
*/
myTreeNode.prototype.onClick_ = function (e) {
goog.base(this, "onClick_", e);
this.onDoubleClick_(e);
};
Changed the creation of the tree variable:
tree = new myTreeControl('root', treeConfig);
Works on single clicks and have not noticed any other things breaking. I've added the annotations so it'll compile easier. You might put the declarations in a separate file with a goog.provide.
Too bad the handleMouseEvent_ of TreeControl is private or you'll just override that one but you can't without changing either TreeControl source or getting compile errors/warnings. Ach wel, jammer.
I'm using jQuery with it's widget factory and using custom events to handle events in my application.
This means that all my event binding looks a lot like:
//...in the widget factory code
$(this.element).addClass('customEventClass');
$(this.element).bind('mysite.loadNextPage', $.proxy(this, 'loadNextPage');
and the events are triggered by doing:
$('.customEventClass').trigger('mysite.loadNextPage');
Because the events are directly bound to the elements that need to receive them, I don't need to have these custom events to bubble up through the DOM. I know I can check whether the even has bubbled up or not by doing this in the event handler code:
if (event.target != event.currentTarget) {
event.stopPropagation();
event.preventDefault();
return;
}
But at the moment because most of the elements that are listening for the custom events don't have a handler registered for 'mysite.loadNextPage' there are 51 events generated where only 1 actually does anything. Is there a way to either:
tell jQuery not to bubble these events at all or
Add a default 'stop propagation' handler to all DOM objects that have class 'customEventClass' to stop them from bubbling up an event that they don't have a specific handler for.
Or are there any other good practices for only triggering events on elements that are interesting in those events, rather than having lots of events be triggered for elements that aren't interested in those events.
You can also return false from your event handler function to stop propagation, that's what I normally use:
Returning false from an event handler will automatically call event.stopPropagation() and event.preventDefault(). A false value can also be passed for the handler as a shorthand for function(){ return false; }. So, $( "a.disabled" ).on( "click", false ); attaches an event handler to all links with class "disabled" that prevents them from being followed when they are clicked and also stops the event from bubbling.
See http://api.jquery.com/on/
It looks like there's no good way to do it with jQuery as it is, but it's very easy to write a new function to allow this.
First I wrote a new function to stop the event from bubbling(, and also log the event why not).
function eventWrapper(event){
var logString = 'Event called: ' + event.type + ":" + event.namespace;
if (jQuery.isFunction(this.log) == true) {
this.log(logString);
}
else if (jQuery.isFunction(Logger.log) == true) {
Logger.log(logString);
}
else{
console.log(logString);
}
event.stopPropagation();
}
And now a new function that is added to jQuery.
// A global GUID counter for objects
guidWrapper: 1,
proxyWrapper: function(wrappedFn, fn, context, wrapFn ) {
var args, proxy, tmp;
if ( typeof context === "string" ) {
tmp = fn[ context ];
context = fn;
fn = tmp;
}
// Quick check to determine if target is callable, in the spec
// this throws a TypeError, but we will just return undefined.
if ( !jQuery.isFunction( fn ) ) {
return undefined;
}
// Simulated bind
args = core_slice.call( arguments, 3 );
proxy = function() {
wrappedFn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
};
// Set the guid of unique handler to the same of original handler, so it can be removed
proxy.guid = fn.guid = fn.guid || jQuery.guid++;
return proxy;
},
And then instead of binding the function like this:
$(this.element).bind('click', $.proxy(this.click, this));
Instead bind it like this.
$(this.element).bind('click', $.proxyWrapper(eventWrapper, this.click, this));
This means that when the event is triggered, the first element that is listening for that event will call event.stopPropagation on the event, and so it won't bubble up to other elements that may also be listening for that event.
I have a class which is used to generate navigation from a variety of interconnected bundles. I have a Navigation service to accomplish this.
In order to connect this service with the other bits of Navigation, I want to allow the other bundles to define their own services which then listen to the event listener and add their navigation items at the proper time.
The problem is, I can't figure out how to have a service listen to an event without first calling that service manually in order to create it.
Any ideas?
To give a more concrete idea, I have something like this:
// Set up as a service in the bundle.
class Navigation {
// ...
protected $dispatcher; // event dispatcher passed in to service
// ...
public function generateNavigation() {
$items = array();
// add some items
$event = new NavigationEvent($items); // custom event
$this->eventDispatcher->dispatchEvent('navigation_event', $event);
}
}
// Set up as a service in some secondary bundle.
class NavigationWorker {
/**
* #param $dispatcher Same instance as Navigation
*/
public function __construct(EventDispatcher $dispatcher) {
$dispatcher->addListener('navigation_event', array($this, 'doSomething'));
}
}
With this set up, it should work if the NavigationWorker is called at some point and is constructed, but I can't always call them directly, so it is never constructed and the listener is never added.
The way I currently do it is to pass all of the NavigationWorkers to Navigation and have it add their listener, but this is very ugly.
See the Event Listener Documentation. Make NavigationWorker and event listener and it won't need to be explicitly constructed.
I'm changing the answer to this because while that set me on the right path, it wasn't the complete answer. That article really only allows you to hook in to pre-defined kernel events. I however needed my own, so I started working back from there.
In the end, I ended up creating my own tags, a compiler pass to process those tasks. I also added my own extension of EventDispatcher, though that wasn't super-necessary (you could just use the normal one).
Here is what the file solution looked like.
Configuration:
parameters:
my_bundle.navigation.event.class: My\Bundle\DependencyInjection\NavigationEvent
my_bundle.event_dispatcher.class: My\Bundle\DependencyInjection\EventDispatcher
my_bundle.navigation.class: My\Bundle\DependencyInjection\NavigationGenerator
my_bundle.navigation_listener1.class: My\Bundle\DependencyInjection\NavigationListener
my_bundle.navigation_listener2.class: My\Bundle\DependencyInjection\NavigationListener
services:
my_bundle.event_dispatcher:
class: %my_bundle.event_dispatcher.class%
my_bundle.navigation:
class: %my_bundle.navigation.class%
arguments:
- #my_bundle.event_dispatcher
my_bundle.navigation_listener1.class:
class: %my_bundle.navigation_listener1.class%
tags:
- { name: my_bundle.event_listener, event: my_bundle.navigation.generate, method: onGenerateNavigation }
my_bundle.navigation_listener2.class:
class: %my_bundle.navigation_listener2.class%
tags:
- { name: my_bundle.event_listener, event: my_bundle.navigation.generate, method: onGenerateNavigation }
CompilerPass:
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
class EventListenerCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('my_bundle.event_dispatcher')) {
return;
}
$definition = $container->getDefinition(
'my_bundle.event_dispatcher'
);
$taggedServices = $container->findTaggedServiceIds(
'my_bundle.event_listener'
);
foreach ($taggedServices as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
$definition->addMethodCall(
'addListener',
array($this->getEventString($attributes['event'], $container), array(new Reference($id), $attributes['method']))
);
}
}
}
protected function getEventString($str, ContainerBuilder $container)
{
preg_match('/(.*)\.([^.]*)$/', $str, $matches);
$parameterName = $matches[1];
$constName = strtoupper($matches[2]);
$eventClass = $container->getParameter($parameterName . '.event.class');
if (!$eventClass) {
throw new Exception('Unable to find parameter: ' . $eventClass . '.event.class');
}
// Return the value of the constant.
return constant($eventClass . '::' . $constName);
}
Add a function like this to your compiler class (something like MyBundleBundle).
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new EventListenerCompilerPass());
}
Now the EventListener will have added listeners for each of those events. You than just implement everything else exactly as you would expect (Navigation dispatches events which it listens too). You can than hook in new event listeners from any bundle, and they don't even need to share a common class/interface.
This also works for any custom event, as long as the object which has the constant for the event is registered in the parameters with ".event.class" at the end (so my_bundle.navigation.generate looks for the parameter my_bundle.navigation.event.class, uses that class and the constant GENERATE).
Hopefully that'll help anyone else looking to do something similar.
I received the following error:
Call to undefined method CI_Loader::plugin() in C:\wamp\www\Code\application\libraries\DX_Auth.php on line 1233
on this code:
function captcha()
{
$this->ci->load->helper('url');
$this->ci->load->plugin('dx_captcha');
$captcha_dir = trim($this->ci->config->item('DX_captcha_path'), './');
Make sure you have moved any array values in application/config/autoload.php from $autoload[‘plugins’] to $autoload[‘helpers’] or you will notice stuff break.
This is the reference
Which version of CI are you using? Plugins has been removed since the 2.x and replaced with helper.
Try to use reCaptcha instead, it has a good library.
You're trying to load a plugin and plugins are not supported, if I remember it right since CI version 2. If that's the case (which seems to be) you need to convert your plugins into helpers.
I think you are trying to use an old version of DX_Auth on new version of CodeIgniter. Current version of DX_Auth is compatible with CI 2.x and is available on github.
A simple way to solve this problem is that you just put this code in your loader.php. The plugin its works. Go to System->Core->Loader.php.
/**
* Load Plugin
*
* This function loads the specified plugin.
*
* #access public
* #param array
* #return void
*/
function plugin($plugins = array())
{
if ( ! is_array($plugins))
{
$plugins = array($plugins);
}
foreach ($plugins as $plugin)
{
$plugin = strtolower(str_replace(EXT, '', str_replace('_pi', '', $plugin)).'_pi');
if (isset($this->_ci_plugins[$plugin]))
{
continue;
}
if (file_exists(APPPATH.'plugins/'.$plugin.EXT))
{
include_once(APPPATH.'plugins/'.$plugin.EXT);
}
else
{
if (file_exists(BASEPATH.'plugins/'.$plugin.EXT))
{
include_once(BASEPATH.'plugins/'.$plugin.EXT);
}
else
{
show_error('Unable to load the requested file: plugins/'.$plugin.EXT);
}
}
$this->_ci_plugins[$plugin] = TRUE;
log_message('debug', 'Plugin loaded: '.$plugin);
}
}
// --------------------------------------------------------------------
/**
* Load Plugins
*
* This is simply an alias to the above function in case the
* user has written the plural form of this function.
*
* #access public
* #param array
* #return void
*/
function plugins($plugins = array())
{
$this->plugin($plugins);
}