Disable scrolling through Ajax.Autocompleter suggestion list via '&' and ')' - autocomplete

I'm using the Ajax.Autocompleter component, which works great - except ampersands in some browsers. The problem is that instead of printing the ampersand sign into the search field, it acts as an 'up' arrow in the ajax suggestion list. This makes my users unable to write names with ampersands into the search box.
http://github.com/madrobby/scriptaculous/wikis/ajax-autocompleter
Specific behavior cases:
If I try to type the ampersand into my search field before the list with suggestions is shown, the ampersand gets added to the search field and all is well.
If I try to type the ampersand after the suggestion list is already visible, it will navigate to the end of the list instead, and nothing will get added to the search field (Chrome).
If I repeat this scenario in IE11, it will also navigate to the end of the list, but also add the ampersand to the search field. However, the ajax search will not get triggered in this case - the suggestion list will remain as it was before I hit the '&' key, regardless of the fact that the search field not ends with a &.
Firefox just adds the '&' char to the search box and refreshes the autocomplete suggestion list. As I would expect.
By trial and error, I noticed that:
When I open F12 in chrome, and debug the javascript, the ampersand gets printed instead of navigating through the suggestion list. When the debug is off it doesn't print anything and navigates instead.
If I use ctrl+v to paste the ampersand into the search field, it works fine in all browsers. On the other hand left alt + 38 does the things mentioned above, same as normal key.
My code:
<input name="searchTxt" id="searchTxt" type="text" autocomplete="off" />
<div id="autocompleteResults" style="display:none;border:1px solid black;background-color:white;text-align:left;"></div>
<script type="text/javascript" language="javascript" charset="utf-8">
new Ajax.Autocompleter('searchTxt','autocompleteResults','someUrlToGetAutocompleteResults', {});
</script>
The only clues I was able to find so far are pretty weak:
Someone having a vaguely similar ampersand problem in chrome and youtube search https://productforums.google.com/forum/#!topic/chrome/mpt4fkaAQPg
Another StackOverflow thread regarding Ajax.Autocompleter, but fairly unrelated How do I make an Ajax.Autocompleter perform a request without typing?
Any help is appreciated - I'll get into studying the Ajax.Autocompleter project in more detail, but since this seemed it might also be something browser-related, I asked with hopes that someone might have at least a partial answer.

After more debugging I found what is causing this problem. The scriptaculous library consists of several .js files - the first problem is in file 'controls.js', line 129.
Here is a function which handles some key strokes - for instance UP and DOWN arrows to navigate through the suggestion list. In chrome, this function gets called after pressing the '&' and '(' keys, instead of the arrows, thus causing the issue.
onKeyPress: function(event) {
if(this.active)
switch(event.keyCode) {
case Event.KEY_TAB:
case Event.KEY_RETURN:
this.selectEntry();
Event.stop(event);
case Event.KEY_ESC:
this.hide();
this.active = false;
Event.stop(event);
return;
case Event.KEY_LEFT:
case Event.KEY_RIGHT:
return;
case Event.KEY_UP:
this.markPrevious();
this.render();
Event.stop(event);
return;
case Event.KEY_DOWN:
this.markNext();
this.render();
Event.stop(event);
return;
}
else
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
(Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
this.changed = true;
this.hasFocus = true;
if(this.observer) clearTimeout(this.observer);
this.observer =
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
}
While this is still not the root cause, it's definitely something to work with and can be used at least for workarounds (if chrome then ignore arrow keys in this function, etc.)

Culprit found!
As it turns out, chrome and IE11 (and possibly more) browsers have the same keyCode value for both the UP arrow, and the '&' key in the 'onKeyPress' function. To read a bit more, see this thread. Some recommend using the 'keyDown' event instead of 'keyPress', i went with a workaround instead.
I have changed the onKeyPress function code in file 'controls.js', line 129. The workaround is not 100% - but it makes ampersand work via shift+7, while retaining the arrow key functionality in firefox.
onKeyPress: function(event) {
//Added this workaround
var is_shift;
if (window.event) {
is_shift = window.event.shiftKey ? true : false;
} else {
is_shift = event.shiftKey ? true : false;
}
if(this.active)
...
case Event.KEY_UP:
if (!is_shift) //Added this workaround
{
this.markPrevious();
this.render();
Event.stop(event);
return;
}
case Event.KEY_DOWN:
if (!isShift) //Added this workaround
{
this.markNext();
this.render();
Event.stop(event);
return;
}
}
...
}
The shift workaround idea comes from here, while the code to get the shift status is taken from here.

Related

How to programmatically collapse space in empty div when google ad does not show

Is there any way to programmatically collapse the empty space that results when a google ad does not show? If so, I would love to see an illustrative example of the same.
Searching around has led me to this official Google resource for accomplishing exactly what I've asked. However, that pertains to Doubleclick for Publishers, which is unfortunately a separate product. I'm pining to know how to handle this for AdSense - some of my users are staring at empty spaces at the moment.
In case it matters, here's an example ad snippet provided by Google AdSense (which I've center-aligned):
<div style="text-align:center">
<ins class="adsbygoogle"
style="display:block"
data-ad-client="ca-pub-0000000000000000"
data-ad-slot="0044031319"
data-ad-format="auto"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
</div>
I know this is old, but since I've been dealing with this now. A simple enough way to do this in jQuery is to check for all elements with the class adsbygoogle that have no child inside.
This selects all the elements with that class and hides them, effectively collapsing them.
$(".adsbygoogle:empty").hide();
You can do a lot of other stuff with it too, like if it's in a div and you need to hide that too, use the $(".adsbygoogle:empty").parent().hide() to collapse it further.
I'm sure this can be done with vanilla javascript as easily. I suggest to run this line of code after the DOM has loaded and even wait like 10 seconds just to see if google populates the ads.
But now it is very simple, just insert this CSS;
<style>
ins[data-ad-status=unfilled] {display:none!important}
</style>
I noticed that the AdSense code broadcasts a MessageEvent, so when I get a resize-me type event, with 'r_nh': 0 key/value pair, I hide the AdSense container (CSS class adsense-ad) manually.
If you have multiple AdSense containers on the same page, you could try to also parse the qid key from the same message.
window.addEventListener("message", (event)=>{
try {
let message = JSON.parse(event.data);
if (message.msg_type === 'resize-me') {
let shouldCollapseAd = false;
for (let index in message.key_value) {
let key = message.key_value[index].key;
let value = message.key_value[index].value;
if (key === 'r_nh' && value === '0') {
shouldCollapseAd = true;
}
}
if (shouldCollapseAd) {
$('.adsense-ad').hide();
}
}
} catch (e) {
}
});
The link provided which refers to DFP Premium at this point redirects to documentation for Google Ad Manager, so it's possible this feature is available without DFP Premium at this point.
Aside from that...
Usually the existence of an iframe element where you expect it is enough to know whether an ad was put where you were expecting one to be put, or not, in my experience.
setTimeout(function () {
if (!document.querySelector('#adcontainer').querySelectorAll('iframe').length > 0) {
document.querySelector('#adcontainer').remove();
}
},1000*2);
As to whether something useful was loaded into that iframe—that isn't something Google is concerned with, so good luck, you'll need it.
I tried to solve it with CSS as Adsense injects various iframe,ins, and divs with various properties.
This code will collapse whitespace but when you ad is in text, it will overflow some of the text, so inline this needs modification:
<style>
iframe { height: auto !important}
ins { height: auto !important}
#google_ads_frame1 { height: auto !important}
.adsbygoogle, #aswift_0_expand, #aswift_0_anchor { height: auto!important} /* there might be more of those */
</style>

Issues with Drupal's autocomplete test with Behat

I'm trying to test a form generated by Drupal where there are some autocompletion to help people do the right choice.
In this form, there are 3 fields that let the user select 3 articles ('Actualités'), and once you start typing things, the auto-completes generates a list of Articles that have a similar title.
Here is an example:
The problem is that I realized while doing some tests that using $this->getSession()->getPage()->fillField() makes Behat loose focus on the field, like if it presses Tab once done filling the field.
So I tried a lot of tricks to gain focus of this field, like the following:
$field = $this->getSession()->getPage()->findField('field_mb_actualites[0][target_id]');
if($field) {
// Solution #1
// the dropdown menu is inside a `ul#ui-id-1` element
$this->getSession()->executeScript("jQuery('#ui-id-1').show();");
// Solution #2
$field->focus();
// Solution #3
// Consists of pressing "shift + tab" for a "reverse tab" and trying to get back to the field
$this->getSession()->getDriver()->keyDown($xpath, 16);
$this->getSession()->getDriver()->keyDown($xpath, 9);
$this->getSession()->getDriver()->keyUp($xpath, 9);
$this->getSession()->getDriver()->keyUp($xpath, 16);
// And I take a screenshot at the end to see if anything worked
file_put_contents('screenshot-focus.png', $this->getSession()->getDriver()->getScreenshot());
}
But I always get the same result, which is the following:
All I need to do once I have the focus is to press the "right" directional arrow to make the dropdown visible again, then the "down" directional arrow to select a suggestion, then "Enter" to confirm the choice.
However, I'm starting to run out of ideas, even though I found a couple questions on Behat's github about this problem, but I couldn't manage to make the answers work. How can I trigger the auto-complete?
Thank you in advance
You can use a JavaScript function to trigger the event like this:
public function triggerEventOn($css_selector){
$function = <<<JS
(function(){
var element = document.querySelector("$css_selector");
var event = document.createEvent('Event');
event.initEvent('change', true, true);
element.dispatchEvent(event);
})()
JS;
try{
$this->getSession()->getDriver()->executeScript($function);
}catch (Exception $e){
}
}
If you want you can also adapt this to fill the field and trigger the event after.
For checking the event that you need to trigger you need to inspect that element and check that ev in the inspector.

CKEditor strips <i> Tag

I'm trying to find a solution to avoid CKEditor, but also the older FCKeditor strips out any
<i> tag from previously inserted content to the db.
Case:
I insert html content to the db, some content contain the <i> elements.
I do this with the CKEditor.
Everything works perfect and the content shows up on the webpage.
But when i want to edit the previously inserted content, the <i> elements are missing.
In my specific case i use:
<i class="fa-icon-fullscreen fa-icon-xxlarge main-color"></i>
Of course if i disable the editor, the content shows up just fine in the textarea.
When the protectedSource solution is used, i tags are no longer stripped, but img tags stop showing up in the WYSIWIG mode of CKEditor (I'm using 4.3.1). The solution that worked better for me is to disable removing empty i tags using CKEDITOR.dtd.$removeEmpty
For example, I added the following to the config.js
// allow i tags to be empty (for font awesome)
CKEDITOR.dtd.$removeEmpty['i'] = false;
Note: This should be placed outside the CKEDITOR.editorConfig = function( config ) function.
I found the solution for this specific problem i ran into with the <i> tag
The original answer i got from drupal forum
The fix or tweak (you name it) for it is to set the following into the ckeditors config.js:
// ALLOW <i></i>
config.protectedSource.push(/<i[^>]*><\/i>/g);
Thanks to Spasticdonkey for pointing me to the link.
Here is what works for me
add the 3 lines of code below in the same order in the drupal ckeditor profile setting
admin/config/content/ckeditor/edit/Full
ADVANCED OPTIONS >> Custom JavaScript configuration
config.allowedContent = true;
config.extraAllowedContent = 'p(*)[*]{*};div(*)[*]{*};li(*)[*]{*};ul(*)[*]{*}';
CKEDITOR.dtd.$removeEmpty.i = 0;
First line is pretty much turning off the advanced filtering
Second line is allowing ALL class (), any style {} and any attribute [*] for the p,div, li and ul.
The last line is for the empty tag...this line works with images...I have found that if you use
config.protectedSource.push(/]*></i>/g);
it strips out the tag while editing.
for 4.3 version ckeditor
in config.js (after config section) paste
CKEDITOR.dtd.$removeEmpty['b'] = false;
and write widget with code
CKEDITOR.plugins.add( 'bwcaret', {
requires: ['widget'/*, 'richcombo'*/],
icons: 'bwcaret',
init: function( editor ) {
editor.widgets.add( 'bwcaret', {
button: 'Create a caret',
template: '<b class="caret"></b>',
allowedContent: 'b(!caret)',
requiredContent: 'b(!caret)',
upcast: function( element ) {
return element.name == 'b' && element.hasClass( 'caret' );
},
});
}
});
There are two possible problems:
Read about Advanced Content Filter. CKEditor is removing elements which are not allowed, but you can extend filter's rules.
However, if the problem is that CKEditor removes empty <i> elements, then you need to find other way of using it. CKEditor is not a WYSIWYG website builder. It is a document editor, so the loaded content must have a meaning. Empty inline element does not have any meaning, therefore it is removed, because otherwise editor would not know what to do with it.
One of the possible solutions in the (near) future, will be to use Widgets system, to handle those empty elements. But for now I advice you to check the CKEDITOR.htmlDataProcessor and short guide how to use it.
i found a permanent solution for it.actually what happened ckeditor removing only blank tag.whatever the tag is, may b <i> tag or <span> tag
if you are using any icon like font-awesome,maeterlize icon etc ...
you can stop it by using below code in your config.js file
CKEDITOR.dtd.$removeEmpty.span = false;
CKEDITOR.dtd.$removeEmpty.i = false;
if you are using more blank tag then you need to add the tag name after $removeEmpty
CKEditor 4 removes emtpy tags, so here you can do trick without changing any config settings.
Replace
<i class="fa-icon-fullscreen fa-icon-xxlarge main-color"></i>
With
<i class="fa-icon-fullscreen fa-icon-xxlarge main-color"> </i>
Now <i></i> tag have content i.e. so CKEditor will not remove that tag.

Unicde Character in Rails View causing gem rack-google-analytics to fail

I was recently attempting to do an update to my views and replace the vanilla asterisk "*" character which was meant to represent a star with the unicode black star "★" (U+2605, "&#x2605"; "&#9733"; 0xE2 0x98 0x85 (e29885)). Everything seemed to work great as I added the character into a string in the appropriate views. One of such views is shown below.
_recent_updates.html.haml
%table.tablesorter#home
%thead
%tr#header
%th#year Year
%th#name Player Name
%th#position Position
%th#school School
%th#stars Stars
%tbody
- #recent_commits.each do |rc|
%tr{:class => cycle("odd", "even")}
%td#class= rc.player.year
%td#name= link_to display_name(rc.player), player_path(rc.player.slug)
%td#position= Position.find(rc.player.position_id).abbr if rc.player.position_id
%td#school= link_to rc.school.name, school_path(rc.school.slug)
%td#stars= "#{display_star(rc.player.vc_star_rating)}★"
I released the update and went along with my business. A couple of days later, I checked Google Analytics to see how traffic was going to the site, and I noticed a precipitous drop to nearly zero. I did some checking as I knew there was a great deal of traffic to the site during this time period, and realized that there was something wrong with my google analytics code. When I looked at the source code for the page in production, here is what I saw.
<--! ...My Page Contents -->
<script type="text/javascript">
if (typeof gaJsHost == 'undefined') {
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
}
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("UA-XXXXXXXX-1");
pageTracker._trackPageview();
} catch(err) {}</scr
It appears that the extra bytes consumed by the unicode character were unaccounted for so that they effectively ate the bottom of my page, causing it to end abruptly. What I should have seen was that script tag should have been ended, as well as the end of the body and html tags like so.
<--! ... My Page Contents -->
<script type="text/javascript">
if (typeof gaJsHost == 'undefined') {
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
}
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("UA-XXXXXXXX-1");
pageTracker._trackPageview();
} catch(err) {}</script>
</body>
</html>
I reverted to the previous change from git (the replacement of asterisks with stars was the only change in the commit in question), and my Google Analytics Tracking Code works fine again, and my script, body and html tags all have their proper closing tags.
My question is two fold.
How do I add the star character back into my view without eating up the end of my code?
I thought UTF-8 was supported out of the box in Rails 3.1, so why is this happening?
I have not seen this issue with Google Analytics in particular, but in general you will get errors if your Rails source files contain Unicode characters without having the
# encoding: UTF-8
line at the top. Double-check that your HAML file is actually encoded as UTF-8 and not anything weird like UTF-16 or a non-Unicode format, then add this tag to the top and see if that solves the issue. You may also try setting Haml::Template.options[:encoding] = "utf-8" in your environment.rb - afaik it should be the default, but may be overridden somewhere.
Rails 3.1 supports Unicode out of the box, but doesn't tolerate Unicode inside its code unless you ask it to. Also note that some database drivers still don't fully support Unicode.
After banging my head against the wall for a few days, and adding some variation of "encoding: UTF-8" to just about every file in my Rails app, I decided to try using the html code &#x2605. First I went to the trusty HTML2HAML converter. It told be that the html code &#x2605 converted to \★ in haml. So I tried that and got a nasty little ISE message. I tried a couple of other variations until finally I stumbled upon a solution.
I created an erb based partial _star.html.erb which I called from my _recent_updates.html.haml file specifically to render the star. Once I did that, everything cleared up and worked like a charm.
I'm still not sure what was going on with the haml, but I'm hoping someone smarter than me can figure it out.
Last line updated:
_recent_updates.html.haml
%table.tablesorter#home
%thead
%tr#header
%th#year Year
%th#name Player Name
%th#position Position
%th#school School
%th#stars Stars
%tbody
- #recent_commits.each do |rc|
%tr{:class => cycle("odd", "even")}
%td#class= rc.player.year
%td#name= link_to display_name(rc.player), player_path(rc.player.slug)
%td#position= Position.find(rc.player.position_id).abbr if rc.player.position_id
%td#school= link_to rc.school.name, school_path(rc.school.slug)
%td#stars
= render 'star', :rc => rc
My new partial
_star.html.erb
<%= "#{display_star(rc.player.vc_star_rating)}" %>★

CollapsiblePanelExtender: Can I initiate collapse/expand from client-side javascript? (AJAX Control Toolkit)

The CollapsiblePanelExtender seems primarily designed to collapse/expand things in response to user mouse events. Is there also a good way to get the extender to collapse/expand things in response to client-side javascript?
In my particular case, I have a number of CollapsiblePanelExtenders (and their corresponding Panels) on a page, and I'm wondering if I could implement an "expand all panels" button by doing something like this strictly on the client side:
for each CollapsiblePanelExtender on this page, call somethingOrOther(extender)
I can implement this logic server-side instead if I did a full postback, but my page takes a long time to load, and so this doesn't seem like it would provide a very slick user experience. Thus I am interested in doing expand/collapse client-side.
It seems like this isn't a use case the AJAX Control Toolkit people had in mind, but I thought I'd check.
Write the following code in the OnClick event of Image/button
<asp:Image ID="img1" runat="server" OnClick="ExpandCollapse()"/>
function ExpandCollapse() {
$find("collapsibleBehavior1").set_Collapsed(true);
$find("collapsibleBehavior2").set_Collapsed(true);
}
Hope this helps!
I have a partly working solution now.
I followed Ian's suggestion and looked through the toolkit source. In CollapsiblePanelBehavior.debug.js, you can that expandPanel() is apparently intended as part of the public interface for the behavior. There's also a get_Collapsed(). The key to accessing these behaviors in javascript seems to be setting the BehaviorID property on your CollapsiblePanelExtender tags in ASP.NET.
I modified the repeater on my page so that the BehaviorIDs are predictible, along these lines:
<ajaxToolkit:CollapsiblePanelExtender
BehaviorID="<%#'collapsebehavior'+DataBinder.Eval(Container.DataItem,'id')%>"
ID="CollapsiblePanelExtender" runat="server" />
This results with behaviors named collapsebehavior1, collapsebehavior2, collapsebehavior3, etc..
With this done, I'm able to expand all the collapsible panels on the client as follows:
function expandAll() {
var i = 0;
while (true) {
i++;
var name = 'collapsebehavior' + i;
var theBehavior = $find(name);
if (theBehavior) {
var isCollapsed = theBehavior.get_Collapsed();
if (isCollapsed) {
theBehavior.expandPanel();
}
} else {
// No more more panels to examine
break;
}
}
}
I'm sure using $find in a loop like that is really inefficient, but that's what I have so far.
Also, it doesn't work on Firefox for some reason. (On FF only the first element expands, and then there's a Javascript error inside the Control Toolkit code.)
This will all seem extremely ugly to all you javascript pros. Maybe I'll clean things up later, or you can help me out.
You can also just toggle the panels to switch between collapsed/expanded states:
function toggle() {
var MenuCollapser = $find("name");
MenuCollapser.togglePanel();
}