root/galaxy-central/static/scripts/jquery.jstore.js @ 2

リビジョン 2, 54.0 KB (コミッタ: hatakeyama, 14 年 前)

import galaxy-central

行番号 
1/*!
2 *  jStore 2.0 - Persistent Client Side Storage
3 *
4 *  Copyright (c) 2010 Eric Garside (http://eric.garside.name/)
5 *  Dual licensed under:
6 *      MIT: http://www.opensource.org/licenses/mit-license.php
7 *      GPLv3: http://www.opensource.org/licenses/gpl-3.0.html
8 *
9 *  ---------------------------
10 *
11 *  jStore Flash Storage Component
12 *
13 *  Copyright (c) 2006 Jeff Lerman (jeff@blip.tv)
14 *  Licensed under the Creative Commons Attribution 3.0 United States License:
15 *      http://creativecommons.org/licenses/by/3.0/us
16 */
17
18"use strict";
19
20/*global Class, window, jQuery, ActiveXObject, google */
21
22/*jslint white: true, browser: true, onevar: true, undef: true, eqeqeq: true, bitwise: true, regexp: false, strict: true, newcap: true, immed: true, maxerr: 50, indent: 4 */
23
24(function ($, window) {
25   
26    //------------------------------
27    //
28    //  Constants
29    //
30    //------------------------------
31   
32    //------------------------------
33    //  Exceptions
34    //------------------------------
35   
36        /**
37         *  An exception thrown by the StorageEngine class whenever its data accessor methods
38         *  are called before the engine is ready to transact data.
39         */
40    var EX_UNSTABLE = 'JSTORE_ENGINE_UNSTABLE',
41   
42        /**
43         *  An exception thrown by jStore whenever an undefined storage engine is referenced for
44         *  some task by an invalid JRI (jStore Resource Identifier).
45         */
46        EX_UNKNOWN = 'JSTORE_UNKNOWN_ENGINE_REQUESTED',
47       
48        /**
49         *  An exception thrown by jStore whenever a given flavor of storage is double defined.
50         */
51        EX_COLLISION = 'JSTORE_ENGINE_NAMESPACE_COLLISION',
52       
53        /**
54         *  An exception thrown by jStore whenever a jri is double applied to a resource.
55         */
56        EX_DUPLICATE = 'JSTORE_RESOURCE_NAMESPACE_COLLISION',
57       
58        /**
59         *  An exception thrown by jStore whenever a given flavor of storage has no defined engine.
60         */
61        EX_UNAVAILABLE = 'JSTORE_ENGINE_UNAVAILABLE',
62       
63        /**
64         *  An exception thrown by jStore whenever an invalid flavor type is used.
65         */
66        EX_INVALID = 'JSTORE_INVALID_FLAVOR',
67       
68    //------------------------------
69    //  Regular Expressions
70    //------------------------------
71   
72        /**
73         *  Regular expression to test property values for being JSON.
74         */
75        RX_JSON = (function ()
76        {
77            try
78            {
79                return new RegExp('^("(\\\\.|[^"\\\\\\n\\r])*?"|[,:{}\\[\\]0-9.\\-+Eaeflnr-u \\n\\r\\t])+?$');
80            }
81            catch (e)
82            {
83                return (/^(true|false|null|\[.*\]|\{.*\}|".*"|\d+|\d+\.\d+)$/);
84            }
85        }()),
86   
87    //------------------------------
88    //  Storage Flavors
89    //------------------------------
90       
91        /**
92         *  The storage flavor identifier for HTML5 local storage.
93         */
94        FLAVOR_LOCAL = 'jstore-html5-local',
95       
96        /**
97         *  The storage flavor identifier for HTML5 database storage.
98         */
99        FLAVOR_SQL = 'jstore-html5-sql',
100       
101        /**
102         *  The storage flavor identifier for Adobe Flash SharedObject storage.
103         */
104        FLAVOR_FLASH = 'jstore-flash',
105       
106        /**
107         *  The storage flavor identifier for Google Gears storage.
108         */
109        FLAVOR_GEARS = 'jstore-google-gears',
110       
111        /**
112         *  The storage flavor identifier for Internet Explorer storage, available to IE7 and IE6.
113         */
114        FLAVOR_MSIE = 'jstore-msie',
115   
116    //------------------------------
117    //
118    //  Property Declaration
119    //
120    //------------------------------
121   
122        /**
123         *  The base StorageEngine class which each "storage flavor" will extend to meet the
124         *  requirements for its specific implementation.
125         */
126        StorageEngine,
127       
128        /**
129         *  The jStore object. Internal to this closure, jStore is referenced by "_". It is
130         *  exposed to jQuery below, and made publicly accessible through jQuery.jStore
131         */
132        _ = {},
133       
134        /**
135         *  The engines available to jStore for use. These are the class definitions for flavored
136         *  storage engines.
137         *
138         *  Signature:
139         *  {
140         *      <storageFlavor>: <flavoredStorageEngineDefinition>,
141         *
142         *      ...
143         *  }
144         */
145        definitions = {},
146       
147        /**
148         *  Active engines instantiated by jStore, indexed by their JRI.
149         *
150         *  Signature:
151         *  {
152         *      <engineJRI>: <engineInstance>,
153         *
154         *      ...
155         *  }
156         */
157        engines = {},
158       
159        /**
160         *  If we are going to be using the flash storage engine, we want to postpone the jStore ready event until the jStore
161         *  isFlashReady flag is also true. This property is set whenever flash is determined to be the storage engine.
162         */
163        waitForFlash = false,
164       
165        /**
166         *  Storage for listeners, indexed by content and event type.
167         *
168         *  Signature:
169         *  {
170         *      <context>:
171         *      {
172         *          <eventType>: [<listener>, ...],
173         *
174         *          ...
175         *      },
176         *
177         *      ...
178         *  }
179         */
180        events = {},
181       
182        /**
183         *  The configuration for this implementation.
184         *
185         *  Signature:
186         *  {
187         *      project: <defaultProjectName>,
188         *
189         *      flash: <pathToFlashBootloader>,
190         *
191         *      json: <pathToJSONFile>,
192         *
193         *      errorCallback: <listenerToNotifyOnError>
194         *  }
195         */
196        configurations =
197        {
198            project: undefined,
199           
200            flash: 'jStore.Flash.html',
201           
202            json: 'browser.json.js'
203        },
204       
205        /**
206         *  The active storage engine, being used to satisfy the get/set/remove functions on the jStore and jQuery
207         *  objects.
208         */
209        active;
210   
211    //------------------------------
212    //
213    //  Internal Methods
214    //
215    //------------------------------
216   
217    /**
218     *  Determine if the given flavor is valid.
219     *
220     *  @param flavor   The flavor to test.
221     *
222     *  @return True if the flavor is valid, false otherwise.
223     */
224    function validFlavor(flavor)
225    {
226        switch (flavor)
227        {
228       
229        case FLAVOR_LOCAL:
230        case FLAVOR_SQL:
231        case FLAVOR_FLASH:
232        case FLAVOR_GEARS:
233        case FLAVOR_MSIE:
234            return true;
235       
236        default:
237            return false;
238       
239        }
240    }
241
242    /**
243     *  Performs enhanced type comparison on an object. This is more reliable method
244     *  of type checking a variable than a simple typeof comparison. The reason is that,
245     *  typeof will reduce to the lowest common type.
246     *
247     *  "typeof []" returns Object, and not Array.
248     *  "typeof {}" returns Object as well.
249     *
250     *  typecheck( [], 'Array' )    :  returns true;
251     *  typecheck( [], 'Object' )   :  returns false;
252     *
253     *  @param type     The variable type to check.
254     *
255     *  @param compare  A string representing the literal type to check.
256     *
257     *  @return True if the variable "type" matches the compare literal.
258     */
259    function typecheck(type, compare)
260    {
261        return !type ? false : type.constructor.toString().match(new RegExp(compare + '\\(\\)', 'i')) !== null;
262    }
263   
264    /**
265     *  If the provided listener is a valid function, it will be triggered with the provided context
266     *  and parameters.
267     *
268     *  @param listener     The listener being triggered.
269     * 
270     *  @param context      The context to provide to the listener.
271     *
272     *  @param parameters   The parameters to pass to the listener as arguments.
273     *
274     *  @return The response of the notified listener.
275     */
276    function notify(listener, context, parameters)
277    {
278        if (typecheck(listener, 'Function'))
279        {
280            return listener.apply(context || _, typecheck(parameters, 'Array') ? parameters : [parameters]);
281        }
282    }
283   
284    /**
285     *  Load the given script.
286     *
287     *  @param path     The path to the file to include.
288     *
289     *  @param listener The listener to notify when the file finishes loading.
290     */
291    function loadScript(path, listener)
292    {
293        $.ajax(
294        {
295            url: path,
296            complete: listener || $.noop(),
297            type: 'GET',
298            dataType: 'script',
299            cache: false
300        }); 
301    }
302   
303    /**
304     *  Checks the type of the value, and returns a value safe to persist in any client-side mechanism.
305     *
306     *  @param value    The value which should be prepared for storage.
307     *
308     *  @return A value safe for storage.
309     */
310    function prepareForStorage(value)
311    {
312        if (value === undefined)
313        {
314            return '';
315        }
316       
317        if (typecheck(value, 'Object') ||
318            typecheck(value, 'Array') ||
319            typecheck(value, 'Function'))
320        {
321            return JSON.stringify(value);
322        }
323       
324        return value;
325    }
326   
327    /**
328     *  Checks the type of the value, and returns a value safe for access in any client-side mechanism.
329     *
330     *  @param value    The value which should be prepared for use.
331     *
332     *  @return A value safe for use.
333     */
334    function prepareForRevival(value)
335    {
336        return RX_JSON.test(value) ? JSON.parse(value) : value;
337    }
338   
339    /**
340     *  Normalize a key before using it, to ensure it's valid.
341     *
342     *  @param key  The key to normalize.
343     *
344     *  @return A normalized key, safe for storage.
345     */
346    function normalizeKey(key)
347    {
348        return key.replace(/^\s+|\s+$/g, "");
349    }
350   
351    /**
352     *  Define a flavored storage engine.
353     *
354     *  @throws EX_COLLISION, EX_INVALID
355     *
356     *  @param flavor       The flavor of engine being defined.
357     *
358     *  @param definition   An object containing the new properties and methods for the engine extension.
359     *
360     *  @param availability A function to invoke which must return a boolean value indicating the
361     *                      availability of the storage flavor on this browser.
362     */
363    function define(flavor, definition, availability)
364    {
365        if (!validFlavor(flavor))
366        {
367            throw EX_INVALID;
368        }
369   
370        if (availability[flavor] !== undefined)
371        {
372            throw EX_COLLISION;
373        }
374       
375        /**
376         *  The logic here has been reworked so unavailable flavors are discarded, so we don't needlessly
377         *  bloat the runtime size of jStore.
378         */
379        if (notify(availability) === true)
380        {
381            _.available[flavor] = true;
382           
383            definition.flavor = flavor;
384           
385            definitions[flavor] = StorageEngine.extend(definition);
386        }
387        else
388        {
389            _.available[flavor] = false;
390           
391            //  Filter the invalid flavor out of the priority list.
392            _.enginePriority = $.map(_.enginePriority, function (engine)
393            {
394                if (engine === flavor)
395                {
396                    return null;
397                }
398                else
399                {
400                    return engine;
401                }
402            });
403        }
404    }
405
406    /**
407     *  Make the jStore library ready.
408     */
409    function makeReady()
410    {
411        if (_.isReady)
412        {
413            return;
414        }
415       
416        if ((waitForFlash && _.isFlashReady) || !waitForFlash)
417        {
418            _.isReady = true;
419            _.trigger('jstore-ready', [engines[active]]);
420        }
421    }
422   
423    /**
424     *  Create a best-fit engine.
425     */
426    function createBestFitEngine()
427    {
428        _.create(_.enginePriority[0], undefined, 'best-fit');
429    }
430   
431    /**
432     *  Get the flash version currently supported in this browser.
433     *
434     *  @return The flash version.
435     */
436    function flashVersion()
437    {
438        // MSIE
439        try
440        {
441            // avoid fp6 minor version lookup issues
442            // see: http://blog.deconcept.com/2006/01/11/getvariable-setvariable-crash-internet-explorer-flash-6/
443            var axo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.6');
444           
445            try
446            {
447                axo.AllowScriptAccess = 'always';
448            }
449            catch (axo_e)
450            {
451                return '6,0,0';
452            }
453           
454            return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
455        }
456       
457        //  Real browsers
458        catch (e)
459        {
460            try
461            {
462                if (navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin)
463                {
464                    return (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]).description.replace(/\D+/g, ",").match(/^,?(.+),?$/)[1];
465                }
466            }
467            catch (flash_e)
468            {}
469        }
470       
471        return '0,0,0';
472    }
473
474    /**
475     *  Flash Detection functions copied from the jQuery Flash Plugin
476     *
477     *  Copyright (c) 2006 Luke Lutman (http://jquery.lukelutman.com/plugins/flash)
478     *
479     *  Dual licensed under the MIT and GPL licenses.
480     *      http://www.opensource.org/licenses/mit-license.php
481     *      http://www.opensource.org/licenses/gpl-license.php
482     *
483     *  @param version  The version to compare to.
484     *
485     *  @return True if the version is greater than or equal to the required version, false otherwise.
486     */
487    function hasFlashVersion(version)
488    {
489        var playerVersion = flashVersion().match(/\d+/g),
490            requiredVersion = version.match(/\d+/g),
491            index = 0,
492            player,
493            required;
494           
495        for (; index < 3; index++)
496        {
497            player = parseInt(playerVersion[index], 10);
498            required = parseInt(requiredVersion[index], 10);
499       
500            //  Player version is less than what is required.
501            if (player < required)
502            {
503                return false;
504            }
505           
506            //  Player version is greater than what is required.
507            else if (player > required)
508            {
509                return true;
510            }
511        }
512       
513        //  Player and required version match exactly.
514        return true;
515    }
516
517    //------------------------------
518    //
519    //  Plugin Definition
520    //
521    //------------------------------
522   
523    //------------------------------
524    //  Error Declaration
525    //------------------------------
526   
527    //------------------------------
528    //  Plugin Creation
529    //------------------------------
530   
531    /**
532     *  The jStore object. Manages a collection of StorageEngines for particular "storage flavors", or the types
533     *  of storage solutions available to each browser.
534     *
535     *  2.0 Version Notes:
536     *
537     *      - The user is now responsible for third-party script includes, with the exception of flash.
538     *
539     *      - jStore has been given sole responsibility for testing engine availability.
540     *
541     *      - For the sake of naming conventions, all property names now start with a lowercase, and are camel-cased.
542     *
543     *  The following properties have been changed since the 1.2.x release:
544     *
545     *      - EngineOrder:      For the sake of naming conventions, renamed to enginePriority.
546     *
547     *  The following properties and methods have been removed since the 1.2.x release:
548     *
549     *      - Availability:     jStore's engines would add their availability tests to this object, so jStore could test
550     *                          them. With the changes to how availability testing works, this property has been removed.
551     *                          A new property, "available" on jStore contains a set of available engines.
552     *
553     *      - Engines:          Formerly contained the definitions of storage engines. This property has been removed, and
554     *                          storage of these definitions has been moved internal to the closure.
555     *     
556     *      - Instances:        Formerly contained instantiated storage engines. This property has been removed, and storage
557     *                          of instantiated engines has been moved internal to the closure.
558     *
559     *      - CurrentEngine:    Formerly contained the active storage engine being used for transacting data through the jStore
560     *                          and/or jQuery objects. This property has been removed, and storage of the current engine has
561     *                          been moved internal to the closure. A new method, "activeEngine" has been added to jQuery to
562     *                          get and set the active engine to use.
563     *
564     *      - defaults:         Formerly used to set the implementation options for jStore. This property has been removed and
565     *                          replaced with a new configuration metho on the jStore object.
566     *
567     *      - delegate:         The delegate class has been removed in favor of a much simpler bind/trigger accessor system, which
568     *                          is accessible contextually through storage engines, or generically through jStore.
569     *     
570     *      + fail:             This registration class bound events on the delegate for jstore-fail events. Instead, use:
571     *                              jStore.bind('jstore-failure', listener);
572     *
573     *      + flashReady:       This registration class bound events on the delegate for flash-ready events. The jstore-ready method
574     *                          now accounts for waiting for flash readyness, if and only if the flash engine is being used. Simply
575     *                          call to jStore.ready().
576     *
577     *      + load:             Replaced with the init() method, which performs the same basic functions as the old load() method. Also,
578     *                          the init function is now domready safe, meaning it wraps itself in a domready listener, so the end user
579     *                          doesn't have to.
580     *
581     *      + FindEngine:       Removed entirely. The functionality provided by this method now implicitly occurs with the new define()
582     *                          system implemented for engine flavors.
583     *
584     *      + setCurrentEngine: Replaced by activeEngine(). Set the current active engine by passing in the JRI.
585     *
586     *      + safeStore:        Replaced by a method internal to this closure, "prepareForStorage".
587     *
588     *      + safeResurrect:    Replaced by a method internal to this closure, "prepareForRevival".
589     *
590     *      + use:              Replaced by "create".
591     */
592    $.extend(_, {
593   
594        //------------------------------
595        //  Properties
596        //------------------------------
597   
598        /**
599         *  The priority order in which engines should be tested for use. The lower their index in the array, the higher
600         *  their priority for use.
601         *
602         *  Be weary when reconfiguring the priority order of engines! jStore will use the first available engine it finds
603         *  based on its priority when autoloading.
604         *
605         *  This array is filtered out as engines are defined, with invalid engines being removed.
606         *
607         *  Signature:
608         *  [FLAVOR_<storageFlavor>, ...]
609         */
610        enginePriority: [FLAVOR_LOCAL, FLAVOR_SQL, FLAVOR_FLASH, FLAVOR_MSIE],
611       
612        /**
613         *  A collection of the availability states of engines, indexed by their flavor.
614         *
615         *  Signature:
616         *  {
617         *      <storageFlavor>: true|false,
618         *
619         *      ...
620         *  }
621         */
622        available: {},
623       
624        /**
625         *  Flag to determine if the jStore library is ready. jStore becomes ready once the dom is ready and all necessary
626         *  startup procedures required by jStore to function properly are completed.
627         */
628        isReady: false,
629       
630        /**
631         *  With the flash storage engine, we have to jump through a couple of hoops before the flash engine is ready to work.
632         *  This flag tracks whether or not the flash storage is available.
633         */
634        isFlashReady: false,
635       
636        /**
637         *  The available engine flavors.
638         */
639        flavors:
640        {
641            local: FLAVOR_LOCAL,
642           
643            sql: FLAVOR_SQL,
644           
645            flash: FLAVOR_FLASH,
646           
647            gears: FLAVOR_GEARS,
648           
649            msie: FLAVOR_MSIE
650        },
651       
652        //------------------------------
653        //  Constructor
654        //------------------------------
655       
656        /**
657         *  Constructor.
658         *
659         *  @throws EX_INVALID
660         *
661         *  @param project          The name of the jStore project. Used to generate a JRI for the engine we create.
662         *
663         *  @param configuration    Optionally, an object containing configuration options for this implementation.
664         *
665         *  @param flavor           Optionally, the flavor of storage to use. If not provided, jStore will pick the
666         *                          best flavor, based on the current browser.
667         *
668         *  @return jStore
669         */
670        init: function (project, configuration, flavor)
671        {   
672            //  Extend our plugin configurations
673            $.extend(configurations, {project: project}, configuration);
674           
675            $(function ()
676            {
677                //  If JSON parsing isn't defined in this browser, include it.
678                if (window.JSON === undefined)
679                {
680                    loadScript(configurations.json);
681                }
682           
683                //  If we have an explicit flavor to use, use it.
684                if (flavor !== undefined)
685                {
686                    _.create(flavor, project, 'default');
687                }
688               
689                //  Otherwise, attempt to create a best-fit engine.
690                else
691                {
692                    createBestFitEngine();
693                }
694            });
695       
696            return _;
697        },
698   
699        //------------------------------
700        //  Methods
701        //------------------------------
702   
703        /**
704         *  Create an instance of a flavored engine.
705         *
706         *  @throws EX_INVALID, EX_UNAVAILABLE, EX_DUPLICATE
707         *
708         *  @param flavor       The flavor to create the engine with.
709         *
710         *  @param project      The project identifier for this instance.
711         *
712         *  @param identifier   Some arbitrary identifier for this project instance of the engine.
713         *
714         *  @return The created instance.
715         */
716        create: function (flavor, project, identifier)
717        {
718            project = project || configurations.project || location.hostname.replace(/\./g, '-') || 'unknown';
719           
720            if (!validFlavor(flavor))
721            {
722                throw EX_INVALID;
723            }
724           
725            if (definitions[flavor] === undefined)
726            {
727                throw EX_UNAVAILABLE;           
728            }
729           
730            var jri = (identifier !== undefined ? identifier + '.' : '') + project + '.' + flavor,
731                engine;
732
733            if (engines[jri] !== undefined)
734            {
735                throw EX_DUPLICATE;
736            }
737           
738            //  Create our engine instance.
739            engine = engines[jri] = new definitions[flavor](project, jri);
740           
741            //  Set up a listener for our jstore-engine-ready event.
742            engine.ready(function ()
743            {
744                _.trigger('jstore-engine-ready', [engine]);
745            });
746           
747            if (flavor === FLAVOR_FLASH && !_.isFlashReady)
748            {
749                if (active === undefined)
750                {
751                    waitForFlash = true;
752                }
753               
754                //  Define a window-accessible function for flash to call via ExternalInterface
755                window.jstore_ready = function ()
756                {
757                    _.isFlashReady = true;
758                    _.trigger('flash-ready');
759                   
760                    if (active === undefined)
761                    {
762                        makeReady();
763                    }
764                   
765                    //  Remove the callback from the window scope, as it is no longer necessary
766                    window.flash_ready = undefined;
767                };
768               
769                window.jstore_error = function (message)
770                {
771                    _.trigger('jstore-error', ['JSTORE_FLASH_EXCEPTION', null, message]);
772                };
773               
774                $('<iframe style="height:1px;width:1px;position:absolute;left:0;top:0;margin-left:-100px;" id="jStoreFlashFrame" src="' +
775                    configurations.flash + '"></iframe>').appendTo('body');   
776            }
777            else if (active === undefined)
778            {
779                active = jri;
780                makeReady();
781            }
782           
783            return engine;
784        },
785       
786        /**
787         *  Fetch an engine by it's JRI.
788         *
789         *  @param jri  The JRI of the engine to retrieve.
790         *
791         *  @return The requested engine.
792         */
793        engine: function (jri)
794        {
795            return engines[jri];
796        },
797   
798        /**
799         *  Returns the active storage engine being used. If a value is passed, sets that engine as the active engine.
800         *
801         *  @throws EX_UNKNOWN
802         *
803         *  @param jri  Optionally, the JRI of the engine to make active, if it should be changed.
804         *
805         *  @return The active storage engine.
806         */
807        activeEngine: function (jri)
808        {
809            if (jri !== undefined)
810            {
811                if (engines[jri] === undefined)
812                {
813                    throw EX_UNKNOWN;
814                }
815                else
816                {
817                    active = jri;
818                }
819            }
820           
821            return engines[active];
822        },
823       
824        /**
825         *  Bind an event listener.
826         *
827         *  @param event    The event to bind a listener on.
828         *
829         *  @param listener The listener to notify when the event occurs.
830         *
831         *  @param context  The context of the binding. A string representing the engine flavor
832         *                  binding the event, or undefined to indicate it's a jStore event.
833         *
834         *  @return jStore
835         */
836        bind: function (event, listener, context)
837        {
838            context = context || 'jstore';
839           
840            if (events[context] === undefined)
841            {
842                events[context] = {};
843            }
844           
845            if (events[context][event] === undefined)
846            {
847                events[context][event] = [listener];
848            }
849            else
850            {
851                events[context][event].push(listener);
852            }
853           
854            return _;
855        },
856       
857        /**
858         *  Trigger an event, notifying any bound listeners.
859         *
860         *  @param event        The event to trigger.
861         *
862         *  @param parameters   Any additional parameters to pass to the listeners being notified.
863         *
864         *  @param context      The context of the binding. A string representing the engine flavor
865         *                      binding the event, or undefined to indicate it's a jStore event.
866         *
867         *  @return jStore
868         */
869        trigger: function (event, parameters, context)
870        {
871            context = context || 'jstore';
872           
873            if (events[context] !== undefined)
874            {
875                if (events[context][event] !== undefined)
876                {
877                    $.each(events[context][event], function ()
878                    {
879                        notify(this, _, parameters);
880                    });
881                }
882            }
883           
884            return _;
885        },
886       
887        /**
888         *      Bind a listener to be notified when jStore causes a non-fatal exception.
889         *
890         *  @param listener The listener to notify when a failure occurs.
891         */
892        error: function (listener)
893        {
894            _.bind('jstore-error', listener);
895        },
896       
897        /**
898         *  Bind a listener to be notified when jStore is ready.
899         *
900         *  @param listener The listener to notify when jStore is ready.
901         *
902         *  @return jStore
903         */
904        ready: function (listener)
905        {
906            if (_.isReady)
907            {
908                notify(listener);
909            }
910            else
911            {
912                _.bind('jstore-ready', listener);
913            }
914           
915            return _;
916        },
917       
918        /**
919         *  Bind a listener to be notified when jStore and the default engine are ready.
920         *
921         *  @param listener The listener to notify when jStore and it's default engine are ready.
922         *
923         *  @return jStore
924         */
925        engineReady: function (listener)
926        {
927            if (_.isReady)
928            {
929                notify(listener);
930            }
931            else
932            {
933                _.bind('jstore-engine-ready', listener);
934            }
935           
936            return _;
937        },
938       
939        /**
940         *  A combined getter/setter for the active engine.
941         *
942         *  @param key      The key of the property to get, or set.
943         *
944         *  @param value    If a valid value is provided, sets the engine.
945         *
946         *  @return The requested property value.
947         */
948        store: function (key, value)
949        {   
950            return value === undefined ? _.get(key) : _.set(key, value);
951        },
952       
953        /**
954         *  Remove a property from the active engine.
955         *
956         *  @param key  The key of the property to remove.
957         *
958         *  @return The value of the property before removal.
959         */
960        remove: function (key)
961        {
962            return _.activeEngine().remove(key);
963        },
964       
965        /**
966         *  Get a property from the active engine.
967         *
968         *  @param key  The key of the property to get.
969         *
970         *  @return The value of the property.
971         */
972        get: function (key)
973        {
974            return _.activeEngine().get(key);
975        },
976       
977        /**
978         *  Set a property on the active engine.
979         *
980         *  @param key      The key of the property to set.
981         *
982         *  @param value    The value to set the property to.
983         *
984         *  @return The new value of the property.
985         */
986        set: function (key, value)
987        {
988            return _.activeEngine().set(key, value);
989        }
990   
991    });
992   
993    //------------------------------
994    //  Core Extension
995    //------------------------------
996   
997    //------------------------------
998    //
999    //  Class Definition
1000    //
1001    //------------------------------
1002   
1003    /**
1004     *  The StorageEngine class is the unified API through which jStore accesses and manipulates
1005     *  the various storage flavors available.
1006     *
1007     *  2.0 Version Notes:
1008     *
1009     *      - All third-party loading is now the responsibility of the developer.
1010     *
1011     *      - The delegate class has been removed entirely. Engines have been given "bind" and "trigger" methods
1012     *          to interact directly with the delegate like-replacement that has been added to jStore.
1013     *
1014     *      - Engine availability has been moved out of the engines themselves, and elevated to a jStore
1015     *          responsibility.
1016     *
1017     *  The following methods have changed since the 1.2.x release:
1018     *
1019     *      - get:          When "get"ting a non-stored property, the get function will now return "undefined"
1020     *                      instead of "null". "null" can now be used as a valid property value.
1021     *
1022     *      - rem:          Renamed to "remove". I always felt dirty about "rem" being vaguely explicit.
1023     *
1024     *  The following properties have been removed since the 1.2.x release:
1025     *
1026     *      - autoload:     Part of the third-party loading logic.
1027     *
1028     *      - hasIncluded:  Part of the third-party loading logic.
1029     * 
1030     *      - includes:     Part of the third-party loading logic.
1031     *
1032     *      - isAvailable:  Part of the availability logic elevated to jStore.
1033     *
1034     *  @throws EX_UNSTABLE
1035     */
1036    StorageEngine = Class.extend({
1037   
1038        //------------------------------
1039        //  Properties
1040        //------------------------------
1041   
1042        /**
1043         *  The project which owns this storage engine.
1044         */
1045        project: undefined,
1046       
1047        /**
1048         *  The JRI (jStore Resource Identifier) acts as a uuid for this specific instance
1049         *  of the storage engine.
1050         */
1051        jri: undefined,
1052       
1053        /**
1054         *  The flavor of this engine.
1055         */
1056        flavor: undefined,
1057       
1058        /**
1059         *  The actual database object which data is transacted through.
1060         */
1061        database: undefined,
1062       
1063        /**
1064         *  A StorageEngine should always respond to fetch requests synchronously. However, some
1065         *  of the storage flavors require callback-based asynchronous access. To get around this,
1066         *  we simlpy require all engines to function off a primary data cache, to allow for
1067         *  synchronous access across all implementations.
1068         *
1069         *  Signature:
1070         *  {
1071         *      <propertyKey>: <propertyValue>,
1072         *
1073         *      ...
1074         *  }
1075         */
1076        data: undefined,
1077       
1078        /**
1079         *  A number of storage engines enforce a size limit as to what they will persist for a given site.
1080         *  This limit is not monitored or computed by jStore currently, and this property will merely give
1081         *  a static indication of the total size alloted to the engine, as defined by the storage flavor.
1082         */
1083        limit: undefined,
1084       
1085        /**
1086         *  Each storage flavor has a different process to go through before it's "ready" to transact data. This
1087         *  property stores the state of the engine's readyness, and uses it to notify listeners whenever jStore
1088         *  is ready to function.
1089         */
1090        isReady: undefined,
1091
1092        //------------------------------
1093        //  Constructor
1094        //------------------------------
1095       
1096        /**
1097         *  Constructor.
1098         *
1099         *  @param project  The project which instantiated this engine.
1100         *
1101         *  @param jri      The uuid assigned to this instance by jStore.
1102         */
1103        init: function (project, jri)
1104        {
1105            this.project = project;
1106            this.jri = jri;
1107            this.data = {};
1108            this.isReady = false;
1109            this.updateCache();
1110        },
1111       
1112        //------------------------------
1113        //  Methods
1114        //------------------------------
1115       
1116        /**
1117         *  Update the cache.
1118         */
1119        updateCache: function ()
1120        {
1121            this.isReady = true;
1122            this.trigger('engine-ready', [this]);
1123        },
1124       
1125        /**
1126         *  Bind a listener to an event dispatched by this engine.
1127         *
1128         *  @param event    The event to bind on.
1129         *
1130         *  @param listener The listener to notify when the event occurs.
1131         */
1132        bind: function (event, listener)
1133        {
1134            _.bind(event, listener, this.jri);
1135        },
1136       
1137        /**
1138         *  Trigger an event, notifying all bound listeners.
1139         *
1140         *  @param event        The event to trigger.
1141         *
1142         *  @param parameters   An optional Array of parameters to pass to the listeners.
1143         */
1144        trigger: function (event, parameters)
1145        {
1146            _.trigger(event, parameters, this.jri);
1147        },
1148       
1149        /**
1150         *  Bind a listener to the StorageEngine's ready event.
1151         *
1152         *  @param listener The listener to notify whenever this engine is ready to transact data.
1153         */
1154        ready: function (listener)
1155        {
1156            if (this.isReady)
1157            {
1158                notify(listener, this);
1159            }
1160            else
1161            {
1162                this.bind('engine-ready', listener);
1163            }
1164        },
1165       
1166        /**
1167         *  Get a property from the StorageEngine.
1168         *
1169         *  @param key  The property key of the data to retrieve.
1170         *
1171         *  @return The property value, or "undefined" if the property isn't stored.
1172         */
1173        get: function (key)
1174        {
1175            this.__interruptAccess();
1176           
1177            return this.data[key];
1178        },
1179       
1180        /**
1181         *  Sets a property in the StorageEngine.
1182         *
1183         *  @param key      The key of the property.
1184         *
1185         *  @param value    The value of the property.
1186         *
1187         *  @return The new value of the property.
1188         */
1189        set: function (key, value)
1190        {
1191            this.__interruptAccess();
1192           
1193            key = normalizeKey(key);
1194           
1195            try
1196            {
1197                this.__set(key, value);
1198            }
1199            catch (e)
1200            {
1201                _.trigger('jstore-error', ['JSTORE_STORAGE_FAILURE', this.jri, e]);
1202            }
1203           
1204            this.data[key] = value;
1205           
1206            return value;
1207        },
1208       
1209        /**
1210         *  Removes a property from the StorageEngine.
1211         *
1212         *  @param key  The property key of the data to remove.
1213         *
1214         *  @return The value of the property, before it was removed.
1215         */
1216        remove: function (key)
1217        {
1218            this.__interruptAccess();
1219           
1220            key = normalizeKey(key);
1221           
1222            try
1223            {
1224                this.__remove(key);
1225            }
1226            catch (e)
1227            {
1228                _.trigger('jstore-error', ['JSTORE_REMOVE_FAILURE', this.jri, e]);
1229            }
1230           
1231            var buffer = this.data[key];
1232           
1233            this.data[key] = undefined;
1234           
1235            return buffer;
1236        },
1237       
1238        //------------------------------
1239        //  Internal Methods
1240        //------------------------------
1241       
1242        /**
1243         *  Ensures the engine is in a stable state for transacting data.
1244         *
1245         *  @throws EX_UNSTABLE
1246         */
1247        __interruptAccess: function ()
1248        {
1249            if (!this.isReady)
1250            {
1251                throw EX_UNSTABLE;
1252            }
1253        },
1254       
1255        /**
1256         *  Sets a property in the StorageEngine. This method should be overloaded to provide actual
1257         *  storage flavor integration.
1258         *
1259         *  @param key      The key of the property.
1260         *
1261         *  @param value    The value of the property.
1262         *
1263         *  @return The new value of the property.
1264         */
1265        __set: function (key, value)
1266        {
1267            return;
1268        },
1269       
1270        /**
1271         *  Removes a property from the StorageEngine. This method should be overloaded to provide actual
1272         *  storage flavor integration.
1273         *
1274         *  @param key  The property key of the data to remove.
1275         *
1276         *  @return The value of the property, before it was removed.
1277         */
1278        __remove: function (key)
1279        {
1280            return;
1281        }
1282       
1283    });
1284       
1285    //------------------------------
1286    //
1287    //  jQuery Hooks
1288    //
1289    //------------------------------
1290   
1291    $.extend($.fn, {
1292   
1293        //------------------------------
1294        //  Methods
1295        //------------------------------
1296   
1297        /**
1298         *  A combined getter/setter for the active engine.
1299         *
1300         *  @param key      The key of the property to get, or set.
1301         *
1302         *  @param value    If a valid value is provided, sets the engine.
1303         *
1304         *  @return jQuery
1305         */
1306        store: function (key, value)
1307        {   
1308            if (value === undefined)
1309            {
1310                _.get(key);
1311            }
1312            else
1313            {
1314                _.set(key, value);
1315            }
1316           
1317            return this;
1318        },
1319       
1320        /**
1321         *  Remove a property from the active engine.
1322         *
1323         *  @param key  The key of the property to remove.
1324         *
1325         *  @return jQuery
1326         */
1327        removeStore: function (key)
1328        {
1329            _.activeEngine().remove(key);
1330           
1331            return this;
1332        },
1333       
1334        /**
1335         *  Get a property from the active engine.
1336         *
1337         *  @param key  The key of the property to get.
1338         *
1339         *  @return The value of the property.
1340         */
1341        getStore: function (key)
1342        {
1343            return _.activeEngine().get(key);
1344        },
1345       
1346        /**
1347         *  Set a property on the active engine.
1348         *
1349         *  @param key      The key of the property to set.
1350         *
1351         *  @param value    The value to set the property to.
1352         *
1353         *  @return jQuery
1354         */
1355        setStore: function (key, value)
1356        {
1357            _.activeEngine().set(key, value);
1358           
1359            return this;
1360        }
1361       
1362    });
1363   
1364    //------------------------------
1365    //
1366    //  Event Bindings
1367    //
1368    //------------------------------
1369   
1370    //------------------------------
1371    //
1372    //  Startup Code
1373    //
1374    //------------------------------
1375   
1376    //------------------------------
1377    //  Expose jStore through jQuery
1378    //------------------------------
1379   
1380    window.jStore = $.jStore = _;
1381   
1382    //------------------------------
1383    //
1384    //  Engine Definitions
1385    //
1386    //------------------------------
1387
1388    //------------------------------
1389    //  Local
1390    //------------------------------
1391   
1392    define(FLAVOR_LOCAL,
1393    {
1394        //------------------------------
1395        //  Properties
1396        //------------------------------
1397       
1398        limit: parseInt(5e5, 16),
1399       
1400        //------------------------------
1401        //  Constructor
1402        //------------------------------
1403   
1404        init: function (project, name)
1405        {   
1406            this.database = window.globalStorage === undefined ? window.localStorage : window.globalStorage[location.hostname];
1407           
1408            this._super(project, name);
1409        },
1410       
1411        //------------------------------
1412        //  Methods
1413        //------------------------------
1414       
1415        updateCache: function ()
1416        {
1417            var key, value;
1418       
1419            for (key in this.database)
1420            {
1421                var has_key = false;
1422                if (this.database.hasOwnProperty) {
1423                    if (this.database.hasOwnProperty(key)) {
1424                        has_key = true;
1425                    }
1426                } else { // IE 8
1427                    if (this.database.getItem(key) !== null) {
1428                        has_key = true;
1429                    }
1430                }
1431                       
1432                if (has_key) {
1433                    value = this.database.getItem(key);
1434           
1435                    //  Gecko's getItem returns {value: 'the value'}, WebKit returns 'the value'
1436                    this.data[key] = prepareForRevival(value && value.value ? value.value : value);
1437                }
1438            }
1439           
1440            this._super();
1441        },
1442       
1443        //------------------------------
1444        //  Internal methods
1445        //------------------------------
1446       
1447        __set: function (key, value)
1448        {
1449            this.database.setItem(key, prepareForStorage(value));
1450        },
1451       
1452        __remove: function (key)
1453        {
1454            this.database.removeItem(key);
1455        }
1456    },
1457   
1458    function ()
1459    {
1460        return window.localStorage !== undefined || window.globalStorage !== undefined;
1461    });
1462   
1463    //------------------------------
1464    //  SQL
1465    //------------------------------
1466   
1467    define(FLAVOR_SQL,
1468    {
1469        //------------------------------
1470        //  Properties
1471        //------------------------------
1472       
1473        limit: parseInt(32e3, 16),
1474       
1475        //------------------------------
1476        //  Constructor
1477        //------------------------------
1478   
1479        init: function (project, name)
1480        {   
1481            this.database = window.openDatabase('jstore-' + project, '1.0', project, this.limit);
1482
1483            if (!this.database)
1484            {
1485                throw 'JSTORE_SQL_NO_DB';
1486            }
1487           
1488            this.database.transaction(function (database)
1489            {
1490                database.executeSql('CREATE TABLE IF NOT EXISTS jstore (k TEXT UNIQUE NOT NULL PRIMARY KEY, v TEXT NOT NULL)');
1491            });
1492           
1493            this._super(project, name);
1494        },
1495       
1496        //------------------------------
1497        //  Methods
1498        //------------------------------
1499       
1500        updateCache: function ()
1501        {
1502            var self = this,
1503                _super = this._super;
1504       
1505            this.database.transaction(function (database)
1506            {
1507                database.executeSql('SELECT k,v FROM jstore', [], function (database, result)
1508                {
1509                    var rows = result.rows,
1510                        index = 0,
1511                        row;
1512                       
1513                    for (; index < rows.length; ++index)
1514                    {
1515                        row = rows.item(index);
1516                        self.data[row.k] = prepareForRevival(row.v);
1517                    }
1518                   
1519                    _super.apply(self);
1520                });
1521            });
1522        },
1523       
1524        //------------------------------
1525        //  Internal methods
1526        //------------------------------
1527       
1528        __set: function (key, value)
1529        {
1530            this.database.transaction(function (database)
1531            {
1532                database.executeSql('INSERT OR REPLACE INTO jstore(k, v) VALUES (?, ?)', [key, prepareForStorage(value)]);
1533            });
1534        },
1535       
1536        __remove: function (key)
1537        {
1538            this.database.transaction(function (database)
1539            {
1540                database.executeSql('DELETE FROM jstore WHERE k = ?', [key]);
1541            });
1542        }
1543    },
1544   
1545    function ()
1546    {
1547        return window.openDatabase !== undefined;
1548    });
1549   
1550    //------------------------------
1551    //  Flash
1552    //------------------------------
1553   
1554    define(FLAVOR_FLASH,
1555    {
1556        //------------------------------
1557        //  Properties
1558        //------------------------------
1559       
1560        limit: -1,
1561       
1562        //------------------------------
1563        //  Constructor
1564        //------------------------------
1565   
1566        init: function (project, name)
1567        {   
1568            var self = this;
1569       
1570            _.bind('flash-ready', function ()
1571            {
1572                self.__flashReadyListener();
1573            });
1574           
1575            this._super(project, name);
1576        },
1577       
1578        //------------------------------
1579        //  Methods
1580        //------------------------------
1581       
1582        updateCache: function (enable)
1583        {
1584            /**
1585             *  The default call to updateCache passes no variable, so we can short circuit the
1586             *  ready state until we explictly call this after flash ready.
1587             */
1588            if (enable === true)
1589            {
1590                var key,
1591                    dataset = this.database.jstore_get_all();
1592           
1593                for (key in dataset)
1594                {
1595                    if (dataset.hasOwnProperty(key))
1596                    {   
1597                        this.data[key] = prepareForRevival(this.database.jstore_get(key));
1598                    }
1599                }
1600           
1601                this._super();
1602            }
1603        },
1604       
1605        //------------------------------
1606        //  Internal methods
1607        //------------------------------
1608       
1609        __set: function (key, value)
1610        {
1611            if (!this.database.jstore_set(key, prepareForStorage(value)))
1612            {
1613                _.trigger('jstore-error', ['JSTORE_STORAGE_FAILURE', this.jri, 'Flash Exception']);
1614            }
1615        },
1616       
1617        __remove: function (key)
1618        {
1619            this.database.jstore_remove(key);
1620        },
1621       
1622        /**
1623         *  Triggered whenever flash is ready.
1624         */
1625        __flashReadyListener: function ()
1626        {
1627            var iFrame = $('#jStoreFlashFrame')[0],
1628                frameDocument;
1629           
1630            //  MSIE
1631            if (iFrame.Document !== undefined && typecheck(iFrame.Document.jStoreFlash.jstore_get, 'Function'))
1632            {
1633                this.database = iFrame.Document.jStoreFlash;
1634            }
1635           
1636            //  Real Browsers
1637            else if (iFrame.contentWindow && iFrame.contentWindow.document)
1638            {
1639                frameDocument = $(iFrame.contentWindow.document);
1640               
1641                //  Webkit
1642                if (typecheck($('object', frameDocument)[0].jstore_get, 'Function'))
1643                {
1644                    this.database = $('object', frameDocument)[0];
1645                }
1646               
1647                //  Gecko
1648                else if (typecheck($('embed', frameDocument)[0].jstore_get, 'Function'))
1649                {
1650                    this.database = $('embed', frameDocument)[0];
1651                }
1652            }
1653           
1654            if (this.database === undefined)
1655            {
1656                throw 'JSTORE_FLASH_REFERENCE_ISSUE';
1657            }
1658            else
1659            {
1660                this.updateCache(true);
1661            }
1662        }
1663    },
1664   
1665    function ()
1666    {
1667        return hasFlashVersion('9.0.0');
1668    });
1669   
1670    //------------------------------
1671    //  Gears
1672    //------------------------------
1673   
1674    define(FLAVOR_GEARS,
1675    {
1676        //------------------------------
1677        //  Properties
1678        //------------------------------
1679       
1680        limit: -1,
1681       
1682        //------------------------------
1683        //  Constructor
1684        //------------------------------
1685   
1686        init: function (project, name)
1687        {   
1688            this.database = google.gears.factory.create('beta.database');
1689            this.database.open('jstore-' + project);
1690            this.database.execute('CREATE TABLE IF NOT EXISTS jstore (k TEXT UNIQUE NOT NULL PRIMARY KEY, v TEXT NOT NULL)');
1691           
1692            this._super(project, name);
1693        },
1694       
1695        //------------------------------
1696        //  Methods
1697        //------------------------------
1698       
1699        updateCache: function ()
1700        {
1701            var result = this.database.execute('SELECT k,v FROM jstore');
1702           
1703            while (result.isValidRow())
1704            {
1705                this.data[result.field(0)] = prepareForRevival(result.field(1));
1706                result.next();
1707            }
1708           
1709            result.close();
1710           
1711            this._super();
1712        },
1713       
1714        //------------------------------
1715        //  Internal methods
1716        //------------------------------
1717       
1718        __set: function (key, value)
1719        {
1720            this.database.execute('BEGIN');
1721            this.database.execute('INSERT OR REPLACE INTO jstore(k, v) VALUES (?, ?)', [key, prepareForStorage(value)]);
1722            this.database.execute('COMMIT');
1723        },
1724       
1725        __remove: function (key)
1726        {
1727            this.database.execute('BEGIN');
1728            this.database.execute('DELETE FROM jstore WHERE k = ?', [key]);
1729            this.database.execute('COMMIT');
1730        }
1731    },
1732   
1733    function ()
1734    {
1735        return window.google !== undefined && window.google.gears !== undefined;
1736    });
1737   
1738    //------------------------------
1739    //  MSIE
1740    //------------------------------
1741       
1742    define(FLAVOR_MSIE,
1743    {
1744        //------------------------------
1745        //  Properties
1746        //------------------------------
1747       
1748        limit: parseInt(1e4, 16),
1749       
1750        //------------------------------
1751        //  Constructor
1752        //------------------------------
1753   
1754        init: function (project, name)
1755        {   
1756            this.database = $('<div style="display:none;behavior:url(\'#default#userData\')" id="jstore-' + project + '"></div>')
1757                .appendTo(document.body).get(0);
1758           
1759            this._super(project, name);
1760        },
1761       
1762        //------------------------------
1763        //  Methods
1764        //------------------------------
1765       
1766        updateCache: function ()
1767        {
1768            this.database.load(this.project);
1769           
1770            var node = document.getElementById('jstore-' + this.project),
1771                xmlDoc = node.XMLDocument,
1772                root,
1773                index = 0;
1774               
1775            if (xmlDoc && xmlDoc.documentElement && xmlDoc.documentElement.attributes)
1776            {
1777                root = xmlDoc.documentElement;
1778               
1779                for (; index < root.attributes.length; ++index)
1780                {
1781                    this.data[root.attributes.item(index).nodeName] = prepareForRevival(root.attributes.item(index).nodeValue);
1782                } 
1783            }
1784           
1785            this._super();
1786        },
1787       
1788        //------------------------------
1789        //  Internal methods
1790        //------------------------------
1791       
1792        __set: function (key, value)
1793        {
1794            this.database.setAttribute(key, prepareForStorage(value));
1795            this.database.save(this.project);
1796        },
1797       
1798        __remove: function (key)
1799        {
1800            this.database.removeAttribute(key);
1801            this.database.save(this.project);
1802        }
1803    },
1804   
1805    function ()
1806    {
1807        return window.ActiveXObject !== undefined;
1808    });
1809   
1810}(jQuery, window));
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。