2011/07/31

Third version for seamless replacement of textareas

Sometime ago I wrote a script that allows to use CKEditor, and at the same time keep using older scripts existing on the page that relied on reading the textarea.value (or even writing it), without having to modify them to use the CKEditor API.

The initial version worked with Firefox and Internet Explorer 8, and shortly after I added some adjustments so that Opera was also supported.

Recently a comment in that second post stated that the script gave errors in Chrome, although it tries to detect if the API that I was using is supported, but of course, webkit guys decided to implement the API but not make it available for native properties. Their statement is that treating native properties as overridable would have a negative effect on performance, and as we all know it's much more important to have a fast browser than a browser that allows the developers to do new things; unless you're in their team then you can write a new API for whatever you need and everyone else should use this API because it will change the world, you'll no longer have to use Flash because now you have this API to overcome other problems that we didn't want to fix. Besides the bug tickets commented previously, here's another one: bug 36423

Ok, enough ranting.

As I said, I've worked to find out the problems and the funny fact about this new version is that it works in Chrome but it still fails in Safari 5.1. So you can use it in your site if can restrict the browser used by your users to IE8+, Firefox 3.5+ , Opera 10+, and Chrome 12+ (I don't really know the oldest version of Firefox, Opera and Chrome where this will work, but I wouldn't expect anyone to use old versions of those browsers)


// Modify the default methods in CKEDITOR.dom.element to use .nativeValue if it's available
CKEDITOR.dom.element.prototype.getValue = function()
{
    if (typeof(this.$.nativeValue) == "undefined")
        return this.$.value;

    return this.$.nativeValue;
}

CKEDITOR.dom.element.prototype.setValue = function( value )
{
    if (typeof(this.$.nativeValue) == "undefined")
        this.$.value = value;
    else
        this.$.nativeValue = value;

    return this;
}

// Hook each textarea with its editor
CKEDITOR.on('instanceCreated', function(e) {
 if (e.editor.element.getName()=="textarea")
 {
  var node = e.editor.element.$;

  // If the .nativeValue hasn't been set for the textarea try to do it now
  if (typeof node.nativeValue == "undefined")
  {
   // for Opera & Firefox
   if (!DefineNativeValue(node))
   {
    // IE8 & Webkit
    if (!DefineValueProperty(node))
    {
     alert("Your browser is buggy. You should upgrade to something newer")
     return;
    }
   }
  }

  node.editor = e.editor;

  // House keeping.
  e.editor.on('destroy', function(e) {
   if (node.editor)
    delete node.editor;
  });
 }
});

// This function alters the behavior of the .value property to work with CKEditor
// It also provides a new property .nativeValue that reflects the original .value
// It can be used with HTMLTextAreaElement.prototype for Firefox, but Opera needs to call it on a textarea instance
function DefineNativeValue(node)
{
 if (!node.__lookupGetter__)
  return false;

    var originalGetter = node.__lookupGetter__("value");
    var originalSetter = node.__lookupSetter__("value");
    if (originalGetter && originalSetter)
    {
        node.__defineGetter__("value", function() {
                // if there's an editor, return its value
                if (this.editor)
                    return this.editor.getData();
                // else return the native value
                return originalGetter.call(this);
                }
            );
        node.__defineSetter__("value", function(data) {
                // If there's an editor, set its value
                if (this.editor) this.editor.setData(data);
                // always set the native value
                originalSetter.call(this, data)
                }
            );

        node.__defineGetter__("nativeValue", function() {
                return originalGetter.call(this);
                }
            );
        node.__defineSetter__("nativeValue", function(data) {
                originalSetter.call(this, data)
                }
            );
        return true
    }
    return false;
}

function DefineValueProperty(node)
{
    var originalValuepropDesc = Object.getOwnPropertyDescriptor(node, "value");

 if (!originalValuepropDesc)
  return false;

 // Safari doesn't allow to overwrite the property (but Chrome does)
 if (!originalValuepropDesc.configurable)
  return false;

    Object.defineProperty(node, "nativeValue",
            {
                get: function() {
                    return ( originalValuepropDesc.get ? originalValuepropDesc.get.call(this) : originalValuepropDesc.value );
                },
                set: function(data) {
                    originalValuepropDesc.set ? originalValuepropDesc.set.call(this, data) : originalValuepropDesc.value = data;
                }
   }
        );

    Object.defineProperty(node, "value",
            {
                get: function() {
                    // if there's an editor, return its value
                    if (this.editor)
                        return this.editor.getData();
                    // else return the native value
                    return this.nativeValue;
                },
                set: function(data) {
                    // If there's an editor, set its value
                    if (this.editor) this.editor.setData(data);
                    // always set the native value
                    this.nativeValue = data;
                }
            }
        );
 return true;
}

// Detection, not really needed, but it can help troubleshoting.
if (Object.defineProperty)
{
    // IE 8 and updated webkits
 // Detect Safari
 if (document.head)
 {
  var test = Object.getOwnPropertyDescriptor(document.head, "innerHTML");
  // IE9
  if (!test)
  {
   if (!DefineValueProperty(HTMLTextAreaElement.prototype))
    alert("Unable to define property override on the prototype");
  }
  else
   if (!test.configurable)
    alert("Safari doesn't allow to overwrite native properties");
 }
}
    else if (document.__defineGetter__)
{
    // FF 3.5 and Opera 10
 // We try to get the innerHTML getter for the body, if it works then getting the value for each textarea will work
 // Detect old webkits
 if (!document.body.__lookupGetter__("innerHTML"))
  alert("Old webkits don't allow to read the originalGetter and Setter for the textarea value");
}
    else
{
    // detect IE8 in compatibility mode...
    if (document.documentMode)
        alert("The page is running in Compatibility Mode (" + document.documentMode + "). Fix that")
    else
        alert("Your version of IE is too old");
}

 

2011/07/13

Too many permissions for simple apps

Yesterday I was badly surprised when Reto Meier added a post pointing to an Android game activated by the voice; that sounded fun (although it's clear that it can't be played in too many places), but I wanted to check it out.

The bad surprise came when the market showed me the required permissions for the App:

  • Record sound (obviously, it needs as that's the differential point)
  • Full internet access (ok, some ads as it's a free app)
  • Read and write the contents of the SD card. This isn't too nice, but some apps and games have extra downloads to keep the app itself smaller and they download the data to the SD. We have to accept it until Android provides a better option (check below for my proposal)
  • Read identity and state of my phone. No, this isn't OK. An app doesn't really require to know anything about my phone. Usually this is enough for me to not install an app.
  • Read SMS. What??? Read my SMS? for a game?, are you joking?. So this app requires full internet access, read all the content of my SD card as well as my identity and my SMS? They can ask for my bank account at the same time.
  • Send SMS. Ok, this is enough. What's the difference between this game and those trojans that have been found claiming to be legit Android Apps? How can a user be able to find out that the developer is really a good person and it's requiring those permissions really based on real requirements instead of being used to send premium SMSs and hide them as those trojans do?

Whenever such application with those huge permissions is mentioned by someone that it's otherwise respected, we can expect that people will download it, and by doing it people will be used to accept that apps might require extra permissions. This will lead to "permission blindness" just like people is now blind to the Ads that appear in some webs, people will get used very soon to the fact that any app might want to read your SMS, your contacts and anything else.

So this will destroy any effectivenes of the permissions system, the typical statement "be careful about what you install" and "it's easy to spot a trojan due to its permissions" will be void. Sum it with the lack of review system in the Android Market and you get a big problem just waiting to happen.

According to some comments in that post, the developers answered in Twitter claiming that the SMS is to upgrade the app to a paid version. So... what's wrong with the Android Market? why don't you offer the full version there instead of using SMSs? Does premium SMS work across countries? how much it's gonna cost some random user in Spain like me a premium SMS sent to the USA? If it's just an internal switch in the app, why isn't this done using the existing internet connection? People pay it in your site and then the app is upgraded to full version. Hell, it can even be done showing the user an unlock code and without internet on the phone app itself!

The requirement to read/write the SD card could be fixed if Android had a better system for apps to store their own data:
Currently those apps create random folders anywhere on the SD card and they remain there after the app has been removed. If instead of getting full access to the SD card they had access to just a custom unique folder, then this could be much better:

  1. Any app can read/write to his own folder in the SD, ex. /Android/data/com.google.android.apps.maps/
  2. That folder is only accessed by that app (or other apps like file managers that really request full SD access), there's no need to state it in the permission list
  3. When the app is removed, the uninstaller also cleans up that folder, so that the app has been really uninstalled and nothing remains. If the user wanted to keep the data he can do a backup of the folder before performing the removal.
  4. In summary: the app itself doesn't really need to know the location of that folder, it just knows that it can safely read and write there, and due to that safety it doesn't require an extra permission at install time. Better for the developer, better for the users.

The Android system really needs an improvement in the way that permissions are granted, the user shouldn't have to worry because an app is requesting weird permissions, also, the Android Market should be much more careful and force a review of any app that requires permissions like send SMS, or perform a combination of read SD and get internet access. When an app just requires internet to show ads it's OK, but if that app is also able to read all the data in your SD card (photos, Titanium backups, etc...) then it should be reviewed to provide a warranty that it's a good app and not something evil.

Enough of bad comments, do you want to know a nice game? (no, this post isn't sponsored and I'm not related to the developer)
Test Trap!, it ask for all the permissions that you can expect from a nice game: none, and it can keep you playing it for a while trying to beat yourself.

 

2011/06/23

Testing Google's Music Beta

Last week I got the invitation to the new Google's "Music Beta" service. I haven't had time to test it too much, but these are my first impressions:

  1. Upload is really slow. That's of course due to my ADSL connection, but you can expect almost a week to upload all your music.
  2. It's strange that the limit is the number of songs (20.000) instead of the size of them
  3. The Uploader recognizes that .ogg files are music, but it refuses to upload them. Why? I've to verify if it has uploaded other "strange" formats that Foobar2000 played happily.
  4. I didn't had all the music properly tagged with ID3 as it was organized with folders and it worked fine, but now those files have crypting names. That's bad in a thousand files library. But the problem is that I have to find out which files have been uploaded wrong, then correct my local copies and also correct the remote copies (or reupload them).
  5. If I edit the metadata of some remote file, I can't download later that updated file. That means that we can't really get rid of our local copies and use just the cloud for storage. In no way I'm gonna get rid of my MP3 player until the phone's batteries last as much and the only way to put music there is with USB, no magic cloud connection.
  6. Some albums are missing the cover picture, and you can edit that with the web interface, but they have forgotten to add drag&drop support to easily upload the new picture. Hey Google, ask the GMail team to give you a hand.
  7. I don't see how playing streaming music in a browser can be interesting in my desktop, I won't use Google Music there until they provide a native application that can use my existing files without streaming them (Hey, I already have them here)
  8. The uploader has given me an interesting error with some files: "No music found in file", but every other player handles those files correctly. Editing the tags didn't fix, so I reencoded and then it accepted them and they were uploaded.
  9. Other times it has stated that there's an error while uploading, so I have to verify album by album if it has been correctly uploaded.

 

2011/04/17

Como usar el DNIe con Firefox 4 en Mac 10.6

This post is in Spanish as it's related only to a problem in Spain with Firefox 4.

DNIe vs Firefox 4

Desde que se lanzó Firefox 4 la gente que usa Mac ha tenido problemas para poder usar el DNIe ya que no permite instalarlo, y si lo tenías instalado con anterioridad no hace nada al intentar acceder a páginas donde lo requieran.

En principio nadie sabía indicar cual era la causa de estos problemas, pero tras crear un ticket en Mozilla respondieron diciendo que el problema es que estabamos intentando usar unas librerías de 32 bits y Firefox 4 se ejecuta por defecto a 64 bits.

Existen los drivers de OpenSC para Mac a 64 bits, pero no sirve de nada instalarlos, ya que los drivers específicos para el DNIe no vienen incluidos y la versión que proporcionan no funcionan con ellos, por lo que tenemos que esperar a que nos publiquen una versión oficial para poder usarlo correctamente.

Solución temporal

Sin embargo, en vez de desinstalar Firefox 4 y volver a la 3.6, podemos configurar fácilmente el ejecutable como indica Willyaranda para que se ejecute a 32 bits y de esa forma sí que podremos instalar la librería necesaria en Firefox y luego usar el DNIe.

Primero vamos a "Aplicaciones" y en el icono de Firefox pinchamos con el botón derecho y escogemos "Obtener información"

En esta ventana se activa la opción de "Abrir en modo de 32 bits"

Y ya está, ahora cuando ejecutemos de nuevo Firefox funcionará en modo de 32 bits y los drivers del DNIe sí que se ejecutarán. Podemos lanzar la web de verificación y nos pedirá que introduzcamos nuestra contraseña.

Si todavía no habíamos instalado el DNIe y esta es la primera vez que lo hacemos, puede pasar que en el paso final tras iniciar de nuevo la sesión en Mac y cuando se lanza automáticamente Firefox, nos diga que no se ha podido instalar el Módulo de seguridad. En ese caso a mi me ha bastado con cerrar Firefox, y al abrirlo de nuevo cargar otra vez el fichero de instalación file:///Library/OpenSC/share/web/instala_modulo_f3.htm y entonces sí que se ha instalado correctamente (tras confirmar los permisos requeridos)

Posibles problemas con los certificados

Otro posible problema con Firefox 4 (pero no exclusivo de Mac ni relacionado exclusivamente con el DNIe) es que al intentar acceder a alguna web con HTTPS nos muestre una ventana de error con este mensaje: "ssl_error_renegotiation_not_allowed"

Dicho problema está explicado por completo en el Wiki de Mozilla: Security:Renegotiation y básicamente se trata de que existe una vulnerabilidad en los protocolos SSL/TLS y desde Firefox 4 han decidido que lo mejor es bloquear dicha vulnerabilidad, e intentar conseguir que todo el mundo actualice sus servidores para evitar el problema.

Por tanto, lo primero que hay que hacer es enviar un correo al contacto de ese sitio web para indicarles la existencia del problema y que deberían solucionarlo lo antes posible. Cuantos más les escribamos, más posibilidades hay de que al fin se decidan a corregirlo aunque no sea más que para no tener que seguir oyéndonos.

Y mientras tanto, pues nosotros podemos hacer unos ajustes en nuestro navegador para poder usar esa web si así lo queremos:

  1. Escribimos about:config en la barra de direcciones y pulsamos enter
  2. Si nos avisa de que podemos estropear algo le decimos que sí, que estamos seguros de lo que queremos hacer.
  3. Escribimos (o pegamos) security.ssl.renego_unrestricted_hosts en el filtro, nos aparecerá la preferencia correspondiente y por defecto como una cadena en blanco.
  4. Le damos a editar y pegamos ahí el dominio donde nos ha dado el error (sin https:// ni las carpetas)

  5. Si hay más de un dominio los separamos con comas.
  6. A continuación filtramos esta otra preferencia security.ssl.treat_unsafe_negotiation_as_broken y la cambiamos a true.

De esta manera ya podremos acceder a ese dominio de forma temporal mientras corrigen los problemas con su servidor y lo dejan bien protegido.

Lo que NO es recomendable es cambiar la configuración de security.ssl.allow_unrestricted_renego_everywhere__temporarily_available_pref ya que eso anula por completo la seguridad y seremos vulverables en cualquier web al problema existente que estaban intentando evitar (es decir, por poder acceder a una web de esta forma se anula la seguridad en todas las webs)

 

2011/04/13

Migrating from FCKeditor is a little easier now

There are tons of site out there that are still using FCKeditor despite the fact that it has been replaced by CKEditor and no work is being done to improve it since long ago.

There are several reasons about that, and one of them is the time cost of upgrading things like the configuration and now there's a little plugin that helps avoid at least one of those problems.

In FCKeditor the definition for Templates (HTML snippets) that the user could insert into the content was available in XML files, but as CKEditor was designed so it could be used in a cross-server environment that meant that the format was changed to JSON in order to load .js files from the server instead of trying to do a XHR that it's quite complex or impossible to do on a foreign server.

But the fact is that most of the people doesn't use CKEditor in such environments and also there are lots of people that have their templates specified in XML files and migrating them to JSON isn't obvious as no tool has been published to carry out that goal. Besides that, I personally find much easier to deal with XML files for the templates instead of the JSON version.

Example of a template in JSON:

   {
    title: 'Image and Title',
    image: 'template1.gif',
    description: 'One main image with a title and text that surround the image.',
    html:
     '<h3>' +
      '<img style="margin-right: 10px" height="100" width="100" align="left"/>' +
      'Type the title here'+
     '</h3>' +
     '<p>' +
      'Type the text here' +
     '</p>'
   },

The same template in XML:

 <Template title="Image and Title" image="template1.gif">
  <Description>One main image with a title and text that surround the image.</Description>
  <Html>
   <![CDATA[
    <img style="MARGIN-RIGHT: 10px" height="100" alt="" width="100" align="left"/>
    <h3>Type the title here</h3>
    Type the text here
   ]]>
  </Html>
 </Template>

In the JSON version you have to use single quotes on each line of the "html" and remember to concatenate everything. In the XML version you just paste whatever you want inside the CDATA comment.

Ok, that's fine. You might like one version or another, but if you want to easily use the XML templates in CKEditor, then upgrade to CKEditor 3.5.3 and add the XmlTemplates plugin.

 

2011/04/09

New beta for WriteArea with CKEditor 3.5.3

Following the release this week of CKEditor 3.5.3, I've updated the WriteArea extension for Firefox to this latest version to include all the bug fixes and enhacements that have been worked on since the last release (check the change log, it's quite a long list as it's usual for every CKEditor release)

In this version, the only patch applied to the core is the latest from this ticket that allows a new button that you can use to switch toolbar on the fly.

The size of the extension has grown again a little due to several languages that have been updated since the previous checks that I did and so I've restored as new they can be correctly used to localize your CKEditor and not have part of the texts in your language and the other half in English.

Direct download link: WriteArea 1.1beta4

2011/03/27

onChange event for CKEditor

It's a somewhat frequent request to see people asking how they can get a notification whenever the content of CKEditor changes.

Some of them are too optimistic:

I've this code <textarea onchange="myfunction()"></textarea> and when I use CKEditor "myfunction" is never called

But most of the people can understand the difference between the original textarea and a CKEditor instance and ask what's the better way to get a notification whenever the content changes, isn't there any built in event for that?

The answer is that no, there's no default event fired whenever something changes, but as I will show here it's quite easy to extend the CKEditor API and generate such event.

Generating a new 'change' event

Although there's no event for "the content has changed" there's something very similar, and that's the Undo system with its saveSnapshot event; whenever something changes it will be called, so we can listen for that event and it will help us greatly with our goal:

       editor.on( 'saveSnapshot', function(e) { somethingChanged(); });

That will take care if the change is something being changed like appying bold or any other style, a new table, pasting, ... it should handle almost everything. But there's one thing that doesn't file a 'saveSnapshot' event, and that's the undo system itself. When Undo or Redo are executed they don't fire that event, so we must listen to them:

        editor.getCommand('undo').on( 'afterUndo', function(e) { somethingChanged(); });
        editor.getCommand('redo').on( 'afterRedo', function(e) { somethingChanged(); });

Ok, Why "something changed"?
Answer: because we are not really sure that something has changed and we aren't really interested to know exactly what has changed, only that something might have changed. Any plugin will fire a "saveSnapshot" before and after any change to work properly, so in our function we will merge all those calls and fire a single event:

        var timer;
        // Avoid firing the event too often
        function somethingChanged()
        {
            if (timer)
                return;

            timer = setTimeout( function() {
                timer = 0;
                editor.fire( 'change' );
            }, editor.config.minimumChangeMilliseconds || 100);
        }

This way the editor will fire a "change" event at most every 100 milliseconds, and you can use editor.checkDirty() to verify if it has really changed (that call might be a little expensive if you are working with big documents, so it's better to avoid calling it too often by using something like the minimum 100 miliseconds that I've added)

Extra checks

To trap every change (and ASAP to avoid delays updating any UI that it's interested in this event I added also a listener for the afterCommandExec (I don't remember now which situation made me add it and I didn't put any comment explaining it :-(  )

        editor.on( 'afterCommandExec', function( event )
        {
            if ( event.data.command.canUndo !== false )
                somethingChanged();
        } );

and also a listener for the keyboard (don't know right now why I didn't listen for the editor.on('key') event; maybe I forgot about it) and new code to handle drag&drop (I've proposed to enhance the Undo system that way in ticket 7422 with some improved code)

        editor.on( 'contentDom', function()
             {
                 editor.document.on( 'keydown', function( event )
                     {
                         // Do not capture CTRL hotkeys.
                         if ( !event.data.$.ctrlKey && !event.data.$.metaKey )
                             somethingChanged();
                     });

                     // Firefox OK
                 editor.document.on( 'drop', function()
                     {
                         somethingChanged();
                     });
                     // IE OK
                 editor.document.getBody().on( 'drop', function()
                     {
                         somethingChanged();
                     });
             });

Ready to use plugin

Here you can download a zip with the full plugin and install instructions onChange event for CKEditor.
Obviously it can be improved, but the important part was to get something working good enough not to win a code contest about the best and most beautiful code.

Note: If you wanna link the plugin from any other site, please link this post so people can get the new versions when they are released instead of linking directly to the zip that will be outdated.

Edit: version 1.1 3rd September 2011

I've fixed an issue with the 'afterUndo' and 'afterRedo' events: they're fired on their respective commands, not on the editor itself; now the Undo and Redo buttons should work correctly. I can't understand how I missed that the first time.

Following the suggestion in the comments, I've added detection for changes in source mode, by keyboard, drag and drop and also on "input" if supported.

Edit: version 1.2 18th September 2011

The new CKEditor 3.6.2 fired the 'saveSnapshot' event too many times, just changing the focus might fire it and generate bogus change events when nothing has changed. Filtered those extra events.

The keyboard listener has been adjusted to ignore movement and other special keyboard events.

Edit: version 1.3 22th December 2011

Avoid firing the event after the editor has been destroyed.

Edit:

I've published a demo showing an usage example.

Edit: version 1.4 7th September 2012

Don't fire events if the editor is readonly, thanks to Ulrich Gabor.
Included code to use Mutation Observers (I'll try to explain it when I have some time).

Edit: version 1.5 20th October 2012

Detect Cut and Paste for IE in source mode thanks to Jacki.

Edit: version 1.6 18th November 2012

Detect multibyte characters thanks to Wouter.

Edit: version 1.7 6th December 2012

Compatibility with Source mode in CKEditor 4.

Note: 15th December 2012

Although the current version doesn't work correctly with CKEditor 4, I don't plan any future update.

Edit: version 1.8 8th June 2013

Use setInterval fix by Roman Minkin.


Notes to self about the Code highlighter plugin used in WriteArea:

  • It doesn't remember the last used language reverting always to Java
  • It doesn't prefill the content with the currently selected text