diff --git a/.sencha/package/build-impl.xml b/.sencha/package/build-impl.xml
new file mode 100644
index 0000000..e242abd
--- /dev/null
+++ b/.sencha/package/build-impl.xml
@@ -0,0 +1,358 @@
+
+
+
+
+
+
+
+ Using Sencha Cmd from ${cmd.dir} for ${ant.file}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.sencha/package/build.properties b/.sencha/package/build.properties
new file mode 100644
index 0000000..83e7e5f
--- /dev/null
+++ b/.sencha/package/build.properties
@@ -0,0 +1,8 @@
+# =============================================================================
+# This file provides an override point for default variables defined in
+# defaults.properties.
+#
+# IMPORTANT - Sencha Cmd will merge your changes with its own during upgrades.
+# To avoid potential merge conflicts avoid making large, sweeping changes to
+# this file.
+# =============================================================================
diff --git a/.sencha/package/codegen.json b/.sencha/package/codegen.json
new file mode 100644
index 0000000..3f0798e
--- /dev/null
+++ b/.sencha/package/codegen.json
@@ -0,0 +1,118 @@
+{
+ "sources": {
+ "package.json.tpl.merge": {
+ "12322b2f0769f491000df8ec0e012dd2d78a7eaf": "eJx1zrEKwjAQgOG9TxE6S9HV2VFcFBdxONJrDW1y8XIRSum7m2pTJzMd/3cHGQuVXunAYrlX5ei79pTmqdx8QQa/wiXNK2hGEOLZwJEbLMWQLURrgYfZzg9iUTksXqOA6bE+YNBsvBhy8+6RXKvqX1PUKA+6gxbz5Qs5LNu7altt19+Q9SDXP9oQW5BPzqknDX0qwhGXxPiMhjGkersXU/EGNatRVA\u003d\u003d"
+ },
+ "theme.html.tpl.merge": {
+ "79ec5194c052d6cc313e842c4e2763fa562c7b70": "eJydVE1vEzEQve+vmO4JEF6nhQNKd3MJRYCQqEg4cHS8k9jFay/2JE1U9b/X3l1KWoKIYln74Tdv3vh55PLs/dfp/Mf1FShqzCQrzxjLAGDq2p3XK0XwQr6Ei9H5BYuPNwXM0Eol4JOVRcZYJAw8haKeJCaUpMng5GpL8HkGc4UNwkfhLYZQ8h7r4xokEWWpZfhrrTdVPnWW0BKb71rMQfZ/VU64JZ5kLiFK+4BUfZ9/YO/ySdYnGmqGL07UIIwBnxJ6rMFo+zOAsDUE6XVLoYtLdXfEBMdgU+WBdgaDQqQcKMoPqjKEHJTHZZXjVjStwaJb4kOCPus+40ZsRL+aQ/CyyouCx3kXsW9orgWpex6/WY2L9aq4Cfmk5H388SkXzlEgL9pn/ANukEKYGS3RRz89HrbhCEmPtkb/f73kfiOsXmKgwXjlPMn1CZq9c3Emvyj1EVuIgDyIEPhwHPy32IlWHqXxuIUTReQ6kGsOmtf1XZ8pjeJW1ysklnpfaBtP7e4RS6MRfqXtGM5H7fbyCRKJpMbwdvQX0rqgSbvIip0uSG/wKe426JfG3Y5ho4NemD34PvtT2ZaFro8YxRqQXv8TgVfPinatkJp2ser9zN32+bD/ku/dHwtX76JR3Ssh3QXzACi8aeE\u003d"
+ },
+ "build.properties.merge": {
+ "8b81315dbe73ce9c08478f4c1d2df84f456efcf5": "eJytkEtOxEAMRPdzipKyhbkBCzQrFnzE5AKetJNYdOzI3UnE7XGA3GC8K5f9/GnwdM84NWhHKeglM2a3VRIXkMJWdg+B2UQrenMk7mnJFSu50C1HXWREOUEUAfr3yzk4M3sVLudTE8bL68f7Z/v81uIRV9ZuJFymhE1yxsQ+ML5tcUReh6BuUkdILbBNkRYXHbDMg1P6BaI10GqSYrXKWoUOSmfaZ+mi88+f6GvvzRTmA8rGPO/6mFMtYPW4fiff97U/al6C1w\u003d\u003d"
+ },
+ "config.rb.tpl.merge": {
+ "33f446bd02c3fd24eb27891582eff6a2e789796b": "eJxLLi2KT8ksUrBVcMvMSdUDMvMSc1M14uPdPH1c4+M1ufJLSwpKS+KLSypzUoGqrPJSi0tSU7gALskTcA\u003d\u003d"
+ },
+ "all.scss.merge": {
+ "da39a3ee5e6b4b0d3255bfef95601890afd80709": "eJwDAAAAAAE\u003d"
+ },
+ "custom.js.merge": {
+ "199e99bbd15c3c0415569425cb21e77c95e9042a": "eJxlj0FOxDAMRfdziq9ZwUjTHIAlYslquIAncdtA4lSxC8PtSdoREmIV6f/4+dmdDjjhbY6KMSZGeycWrmQcQAqCGlWLMmEpUQzXb1xY/Ex4zgFnRMNXTAlSWseovCTybbbUDl6XsJHa1FH3sYX8B03cqqlS4OPQ//2V8CQ7K5fPriEBNjPU17gYjCZE6UnmYbacfj/GsaUNslUIhbVzu5lwq/2qVjIohGixCCVkkjiyWrOFzqWaXw0sViPr0IRYGVQ7yq+55X2HdObg7meo45udt4XnKyk7Je0Z5SWxqyyB6/Cu/Uh3ODj3crNhN28ar/f1D49P/7rLXUd7+QPuPI9g"
+ },
+ "testing.properties.merge": {
+ "e65f969c42eb4f355c850fc58fea852582f20db8": "eJyVkUFywyAQBO9+xVb5oIutH/gX+QCCkbUOAooFOf59FsmqpHKKOFEwOzM0Z7r9f53O9DGx0Mge5DBygFDKMSEX1m0VOBpepLqhsndXnpPvv2Z/oefEdiKdLRNoMAJqdyqMI5lAJiXP1hSOQbbZ5msh0mskmuOvnDHHWY32JjbmDEkxOCqxBai6K5DC4d693RAWzjHMCOVCkmB5ZLhW9EWdINjJtBJv9T7cU0vXsk/2rWwxn9AisHA6AooLcgNhqi8riYXdimAn0P+07vXsCOuD8rNimLWaiDKkmBrK7UOUyR0B2RRQdzXedyp+CMVaUi0rQn3ninMxvurPspjBQ/54jjHvYLbHycGKG5Fm2SIf0u/ut9M3l43NIg\u003d\u003d"
+ },
+ "sencha.cfg.tpl.merge": {
+ "6d1982cce48163a98dc46012d1d0cdfa209fbda6": "eJzFVdFq2zAUfc9XXNzBWmicvg0GgXZlD9vDWsge96LIcqJFljRJTuqN/vuOJNvJmqbbYLASim1dnXPuuVdXZ/R5LUizRpCpKeDZMr5hK0FT8mvTqooaFvg6LRUxriDrjBUudCQ1lbM+vvzqjZ6cTYbXGDr/YTerT3h4nEzORiKPEEHB0G4tE7D0A+lrT4ubxYK4cQ5xRle+TPsqUbNWBdoy1UalgCveP4SCGsG0BwYLSWEtlfBZl2fez7zjdM50NbxvmbvoE+IKH1IwwOLehOeMCXuVJX3QZLDoaGdc5S8Ta2mZFqq8j/+hgFkIMADZc85SxCxFlJ57X2I5WmDIh06Jkbx2piGmuz3lJXkRsimwaKmY3kBFnb/gl75cZsk6hmTAY12daSE6FtC2IduaUj1QiT3PKR1rGAPLUdkc4bmOWUn0jJumYVMvLHMsiIqU9CE2Um1UJZxHiYXbZ4uaykqkinoBpOF702LXUpB4sEpyGVSH0G+tdECMJggo56qt8IrSRt5lK1V1KDVBWRbW81df2qurN++GhUq6x5js3yrHo9kK5yAYPXij1L63YimAFtloJ7ECgSu5RTkYBbaKW4ue/m6AKPpuBztrg0ELSs6U6oAzpgroldAi62EWbbXs8pGEcULz/ogCQkkdPYlBOHdv4cMZEc1m1z0WXT/lP7BqTOtZq8bVp4ZlY/qKpnYqYGvRFzWd0xzsBXN8jRRq42hxu1gAKHuXjul+nHxkW7bgTtowdkLrsa/PGsmhG2CU0UdNCebTxe7b+w8SwDjw/ykDUL+cAQLGDCL5UeuQbKxxITUOTkkkTFmls4GehHjRp3jELsJv/EPADCRpJJwwEv3XT3BDVsWZXsQuXbM8zeMdkqQUZNqAGXQoIp9f0D2rIK0OV0bPlpznRgcmMfDFA2us+sXhc3+R6nV4nxxS9lv8SdYh4EVi3y6n/S4/HscBhu40yoI7SubWKWqH4YmbY1OkeZEv1tDZX4QBEtfkaV0DHXTN/+kfNN1i8uIGus83ukROU7pPteRpRX5P3mISCWV2B8MHNWdbgzwb4VZplhtdoxQBM19iLmqxg3eYJT5un/wEixPeXA\u003d\u003d"
+ }
+ },
+ "targets": {
+ ".sencha/package/testing.properties": {
+ "source": "testing.properties.merge",
+ "version": "e65f969c42eb4f355c850fc58fea852582f20db8",
+ "parameters": {
+ "pkgName": "ext.ux.data.proxy.websocket",
+ "senchadir": ".sencha",
+ "touchRelPath": "../${touch.dir}",
+ "extRelPath": "../../ext",
+ "pkgType": "code"
+ }
+ },
+ ".sencha/package/build.properties": {
+ "source": "build.properties.merge",
+ "version": "8b81315dbe73ce9c08478f4c1d2df84f456efcf5",
+ "parameters": {
+ "pkgName": "ext.ux.data.proxy.websocket",
+ "senchadir": ".sencha",
+ "touchRelPath": "../${touch.dir}",
+ "extRelPath": "../../ext",
+ "pkgType": "code"
+ }
+ },
+ "package.json": {
+ "source": "package.json.tpl.merge",
+ "version": "12322b2f0769f491000df8ec0e012dd2d78a7eaf",
+ "parameters": {
+ "pkgName": "ext.ux.data.proxy.websocket",
+ "senchadir": ".sencha",
+ "touchRelPath": "../${touch.dir}",
+ "extRelPath": "../../ext",
+ "pkgType": "code"
+ }
+ },
+ "sass/example/theme.html": {
+ "source": "theme.html.tpl.merge",
+ "version": "79ec5194c052d6cc313e842c4e2763fa562c7b70",
+ "parameters": {
+ "pkgName": "ext.ux.data.proxy.websocket",
+ "senchadir": ".sencha",
+ "touchRelPath": "../${touch.dir}",
+ "extRelPath": "../../ext",
+ "pkgType": "code"
+ }
+ },
+ "sass/example/custom.js": {
+ "source": "custom.js.merge",
+ "version": "199e99bbd15c3c0415569425cb21e77c95e9042a",
+ "parameters": {
+ "pkgName": "ext.ux.data.proxy.websocket",
+ "senchadir": ".sencha",
+ "touchRelPath": "../${touch.dir}",
+ "extRelPath": "../../ext",
+ "pkgType": "code"
+ }
+ },
+ "sass/etc/all.scss": {
+ "source": "all.scss.merge",
+ "version": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+ "parameters": {
+ "pkgName": "ext.ux.data.proxy.websocket",
+ "senchadir": ".sencha",
+ "touchRelPath": "../${touch.dir}",
+ "extRelPath": "../../ext",
+ "pkgType": "code"
+ }
+ },
+ "sass/config.rb": {
+ "source": "config.rb.tpl.merge",
+ "version": "33f446bd02c3fd24eb27891582eff6a2e789796b",
+ "parameters": {
+ "pkgName": "ext.ux.data.proxy.websocket",
+ "senchadir": ".sencha",
+ "touchRelPath": "../${touch.dir}",
+ "extRelPath": "../../ext",
+ "pkgType": "code"
+ }
+ },
+ ".sencha/package/sencha.cfg": {
+ "source": "sencha.cfg.tpl.merge",
+ "version": "6d1982cce48163a98dc46012d1d0cdfa209fbda6",
+ "parameters": {
+ "pkgName": "ext.ux.data.proxy.websocket",
+ "senchadir": ".sencha",
+ "touchRelPath": "../${touch.dir}",
+ "extRelPath": "../../ext",
+ "pkgType": "code"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/.sencha/package/defaults.properties b/.sencha/package/defaults.properties
new file mode 100644
index 0000000..dab58b1
--- /dev/null
+++ b/.sencha/package/defaults.properties
@@ -0,0 +1,155 @@
+# =============================================================================
+# This file defines properties used by build-impl.xml and the associated
+# *-impl.xml files (sass-impl.xml, js-impl.xml, etc.), which are the core of
+# the applications build process.
+#
+# IMPORTANT - This file is not modifiable by a package, and will be overwritten
+# during each app upgrade. Please use build.properties for defining package
+# customizations to these properties.
+# =============================================================================
+
+# ===========================================
+# properties defining various directory
+# locations
+# ===========================================
+build.dir=${package.build.dir}
+build.resources.dir=${build.dir}/resources
+package.resources.dir=${package.dir}/resources
+package.sass.dir=${package.dir}/sass
+package.licenses.dir=${package.dir}/licenses
+
+# ===========================================
+# definitions of various file name patterns
+# used for output artifacts
+# ===========================================
+build.name.prefix=${build.dir}/${package.name}
+build.name.css.prefix=${build.resources.dir}/${package.name}
+build.name.ruby=config.rb
+
+build.debug.suffix=-debug
+build.all.suffix=-all
+build.rtl.suffix=-rtl
+
+build.all.debug.suffix=${build.all.suffix}${build.debug.suffix}
+build.all.rtl.suffix=${build.all.suffix}${build.rtl.suffix}
+build.all.rtl.debug.suffix=${build.all.suffix}${build.rtl.suffix}${build.debug.suffix}
+
+# ===========================================
+# define the output js file names for dev,
+# debug, and compressed (no suffix)
+# ===========================================
+build.all.js=${build.name.prefix}.js
+build.all.debug.js=${build.name.prefix}${build.debug.suffix}.js
+
+# ===========================================
+# output file names for the scss files
+# ===========================================
+build.all.scss=${build.name.prefix}${build.all.debug.suffix}.scss
+build.all.rtl.scss=${build.name.prefix}${build.all.rtl.debug.suffix}.scss
+
+# ===========================================
+# output file names for the css files
+# generated from the scss files by running
+# a compass compilation
+# ===========================================
+build.all.css.debug.prefix=${package.name}${build.all.debug.suffix}
+build.all.css.debug=${build.resources.dir}/${build.all.css.debug.prefix}.css
+build.all.rtl.css.debug.prefix=${package.name}${build.all.rtl.debug.suffix}
+build.all.rtl.css.debug=${build.resources.dir}/${build.all.rtl.css.debug.prefix}.css
+build.all.css.prefix=${package.name}${build.all.suffix}
+build.all.css=${build.resources.dir}/${build.all.css.prefix}.css
+build.all.rtl.css.prefix=${package.name}${build.all.rtl.suffix}
+build.all.rtl.css=${build.resources.dir}/${build.all.rtl.css.prefix}.css
+
+build.all.ruby=${build.dir}/${build.name.ruby}
+
+# ===========================================
+# options to pass to the 'sencha fs slice' command
+# ===========================================
+build.slice.options=
+
+# ===========================================
+# preprocessor options used when generating
+# concatenated js output files
+# ===========================================
+build.compile.js.debug.options=debug:true
+build.compile.js.options=debug:false
+
+# enables / disables removing text references from
+# package js build files
+build.remove.references=false
+
+# This property can be modified to change general build options
+# such as excluding files from the set. The format expects newlines
+# for each argument, for example:
+#
+# build.operations=\
+# exclude\n \
+# -namespace=Ext\n
+#
+# NOTE: modifications to build.operations are intended to be
+# placed in an override of the "-after-init" target, where it
+# can be calculated based on other
+# ant properties
+#
+# build.operations=
+
+# ===========================================
+# compression option used to generate '-all'
+# js output file
+# ===========================================
+build.compile.js.compress=+yui
+
+# ===========================================
+# selector count threshold to use when
+# splitting a single css file into multiple
+# css files (IE selector limit workaround)
+# ===========================================
+build.css.selector.limit=4095
+
+# controls the ruby command used to execute compass. a full path
+# to ruby may be specified rather than allowing the system shell
+# to resolve the command
+build.ruby.path=ruby
+
+# controls the working directory of the child compass process
+# and the output location for the .sass-cache folder
+compass.working.dir=${build.dir}
+
+# enables / disables console highlighting for compass
+compass.compile.boring=false
+
+# enables / disables forced rebuilds for compass
+compass.compile.force=true
+
+# enables / disables stack traces in compass failure output
+compass.compile.trace=true
+
+# ===========================================
+# Options for sub-packages
+
+# Set to true/1 to enable build.version inheritance by sub-pacakges
+build.subpkgs.inherit.version=0
+
+# ===========================================
+# theme slicing example page settings
+# ===========================================
+package.example.dir=${package.dir}/sass/example
+package.example.base=${build.all.rtl.css.debug.prefix}
+package.example.css.rel=resources/${package.example.base}.css
+package.example.css=${build.dir}/${package.example.css.rel}
+package.example.scss=${build.dir}/${package.example.base}.scss
+package.example.theme.html=${package.example.dir}/theme.html
+
+bootstrap.base.path=${package.example.dir}
+bootstrap.example.js=${package.example.dir}/bootstrap.js
+
+
+# ===========================================
+# options controlling output packaging
+# operations for output '.pkg' file
+# ===========================================
+pkg.build.dir=${workspace.build.dir}/${package.name}
+pkg.file.name=${package.name}.pkg
+pkg.includes=**/*
+pkg.excludes=package.json
diff --git a/.sencha/package/find-cmd-impl.xml b/.sencha/package/find-cmd-impl.xml
new file mode 100644
index 0000000..55d6826
--- /dev/null
+++ b/.sencha/package/find-cmd-impl.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ source ~/.bash_profile; sencha which -p cmd.dir -o '$cmddir$'
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.sencha/package/init-impl.xml b/.sencha/package/init-impl.xml
new file mode 100644
index 0000000..a561649
--- /dev/null
+++ b/.sencha/package/init-impl.xml
@@ -0,0 +1,266 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Switch package version to ${build.version}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.sencha/package/js-impl.xml b/.sencha/package/js-impl.xml
new file mode 100644
index 0000000..50e6992
--- /dev/null
+++ b/.sencha/package/js-impl.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.sencha/package/plugin.xml b/.sencha/package/plugin.xml
new file mode 100644
index 0000000..d57eba8
--- /dev/null
+++ b/.sencha/package/plugin.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
diff --git a/.sencha/package/resources-impl.xml b/.sencha/package/resources-impl.xml
new file mode 100644
index 0000000..19e2d48
--- /dev/null
+++ b/.sencha/package/resources-impl.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+ Merging resources from base package ${base.path}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Merging resources from current package ${package.resources.dir}
+
+
+
+
+
\ No newline at end of file
diff --git a/.sencha/package/sass-impl.xml b/.sencha/package/sass-impl.xml
new file mode 100644
index 0000000..d86e2d6
--- /dev/null
+++ b/.sencha/package/sass-impl.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.sencha/package/sencha.cfg b/.sencha/package/sencha.cfg
new file mode 100644
index 0000000..b074305
--- /dev/null
+++ b/.sencha/package/sencha.cfg
@@ -0,0 +1,60 @@
+# The name of the package - should match the "name" property in ./package.json
+#
+package.name=ext.ux.data.proxy.websocket
+
+# The namespace to which this package's SASS corresponds. The default value of
+# "Ext" means that the files in ./sass/src (and ./sass/var) match classes in
+# the Ext" root namespace. In other words, "Ext.panel.Panel" maps to
+# ./sass/src/panel/Panel.scss.
+#
+# To style classes from any namespace, set this to blank. If this is blank,
+# then to style "Ext.panel.Panel" you would put SASS in
+# ./sass/src/Ext/panel/Panel.scss.
+#
+package.sass.namespace=Ext
+
+# This is the comma-separated list of folders where classes reside. These
+# classes must be explicitly required to be included in the build.
+#
+package.classpath=${package.dir}/src
+
+# This is the comma-separated list of folders of overrides. All files in this
+# path will be given a tag of "packageOverrides" which is automatically
+# required in generated apps by the presence of this line in app.js:
+#
+# //@require @packageOverrides
+#
+package.overrides=${package.dir}/overrides
+
+# This is the folder where SASS "src" resides. This is searched for SCSS
+# files that match the JavaScript classes used by the application.
+#
+package.sass.srcpath=${package.dir}/sass/src
+
+# This is the folder where SASS "vars" resides. This is searched for SCSS
+# files that match the JavaScript classes used by the application.
+#
+package.sass.varpath=${package.dir}/sass/var
+
+# This file is automatically imported into the SASS build before "vars".
+#
+package.sass.etcpath=${package.dir}/sass/etc/all.scss
+
+# This is the folder in which to place "sencha packaage build" output.
+#
+package.build.dir=${package.dir}/build
+
+# The folder that contains example application(s) for this package.
+#
+package.examples.dir=${package.dir}/examples
+
+# The folder that contains sub-packages of this package. Only valid for "framework"
+# package type.
+#
+package.subpkgs.dir=${package.dir}/packages
+
+#==============================================================================
+# Custom Properties - Place customizations below this line to avoid merge
+# conflicts with newer versions
+
+package.cmd.version=4.0.4.84
diff --git a/.sencha/package/slice-impl.xml b/.sencha/package/slice-impl.xml
new file mode 100644
index 0000000..8ca45dc
--- /dev/null
+++ b/.sencha/package/slice-impl.xml
@@ -0,0 +1,111 @@
+
+
+
+
+/**
+ * This file is generated by Sencha Cmd and should NOT be edited. It is
+ * provided to support globbing requires, custom xtypes, and other
+ * metadata-driven class system features
+ */
+
+
+
+
+
+
+
+
+
+
+
+/*
+ * This file is generated by Sencha Cmd and should NOT be edited. It redirects
+ * to the most recently built CSS file for the application to allow theme.html
+ * to load properly for image slicing (required to support non-CSS3 browsers
+ * such as IE9 and below).
+ */
+@import '${package.example.css.path}';
+
+
+
+
+
+ Capture theme image to ${build.dir}/theme-capture.png
+
+
+
+
+
+
+ Slicing theme images to ${build.resources.dir}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.sencha/package/sub-builds.xml b/.sencha/package/sub-builds.xml
new file mode 100644
index 0000000..90f648e
--- /dev/null
+++ b/.sencha/package/sub-builds.xml
@@ -0,0 +1,182 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ package
+ upgrade
+
+
+
+
+
+
+
+
+
+ Building example in @{example-dir}
+
+
+
+
+
+
+
+
+ Upgrading example in @{example-dir}
+
+
+ app
+ upgrade
+
+
+
+
+
+
+
+
+
+ Cleaning example in @{example-dir}
+
+
+
+
+
\ No newline at end of file
diff --git a/.sencha/package/testing.properties b/.sencha/package/testing.properties
new file mode 100644
index 0000000..60749a3
--- /dev/null
+++ b/.sencha/package/testing.properties
@@ -0,0 +1,17 @@
+# ===========================================
+# This file defines properties used by
+# build-impl.xml, which is the base impl
+# of an applications build process. The
+# properties from this file correspond to the
+# 'testing' build environment, specified
+# by 'sencha app build testing'. These will
+# take precedence over defaults provided by
+# build.properties.
+# ===========================================
+
+# ===========================================
+# compression option used to generate '-all'
+# js output file. this value disables
+# compression for testing builds
+# ===========================================
+build.compile.js.compress=
diff --git a/Gruntfile.js b/Gruntfile.js
index 6f3fa6e..8eaf14c 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -5,7 +5,7 @@ module.exports = function (grunt) {
uglify: {
dist: {
files: {
- 'WebSocket.min.js': 'WebSocket.js'
+ 'WebSocketProxy.min.js': 'WebSocketProxy.js'
}
}
} ,
@@ -22,7 +22,7 @@ module.exports = function (grunt) {
smarttabs: true ,
loopfunc: true
} ,
- src: ['WebSocket.js']
+ src: ['WebSocketProxy.js']
}
}
});
diff --git a/architect/WebSocket.Definition.js b/architect/WebSocket.Definition.js
new file mode 100644
index 0000000..7b9d41a
--- /dev/null
+++ b/architect/WebSocket.Definition.js
@@ -0,0 +1,9 @@
+{
+ "className": "Ext.ux.WebSocket",
+ "classAlias": "widget.websocket",
+ "toolbox": {
+ "name": "WebSocket Wrapper",
+ "category": "Data Proxies",
+ "groups": ["Networking"]
+ }
+}
\ No newline at end of file
diff --git a/architect/WebSocketProxy.Definition.js b/architect/WebSocketProxy.Definition.js
new file mode 100644
index 0000000..7831b1f
--- /dev/null
+++ b/architect/WebSocketProxy.Definition.js
@@ -0,0 +1,10 @@
+{
+ "className": "Ext.ux.data.proxy.WebSocket",
+ "classAlias": "proxy.websocket",
+ "inherits": "Ext.data.proxy.Proxy",
+ "toolbox": {
+ "name": "WebSocket Proxy",
+ "category": "Data Proxies",
+ "groups": ["Data"]
+ }
+}
\ No newline at end of file
diff --git a/bower.json b/bower.json
index b046efd..4c7eef6 100644
--- a/bower.json
+++ b/bower.json
@@ -7,7 +7,7 @@
],
"description": "An easy-to-use implementation of the ExtJS/Sencha Touch proxy, using HTML5 WebSocket",
"main": [
- "WebSocket.js"
+ "WebSocketProxy.js"
],
"keywords": [
"sencha",
diff --git a/demo/demo.js b/demo/demo.js
index 4b6033a..82e782c 100644
--- a/demo/demo.js
+++ b/demo/demo.js
@@ -2,7 +2,7 @@ Ext.Loader.setConfig({
enabled: true,
paths: {
'Ext.ux.WebSocket': '../bower_components/ext.ux.websocket/WebSocket.js',
- 'Ext.ux.data.proxy.WebSocket': '../WebSocket.js'
+ 'Ext.ux.data.proxy.WebSocket': '../WebSocketProxy.js'
}
});
diff --git a/package.json b/package.json
index 948d277..4459fd7 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "Ext.ux.data.proxy.WebSocket",
"version": "0.0.0",
"description": "An easy-to-use implementation of the ExtJS/Sencha Touch proxy, using HTML5 WebSocket",
- "main": "WebSocket.js",
+ "main": "WebSocketProxy.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
@@ -26,10 +26,36 @@
"homepage": "https://github.com/wilk/Ext.ux.data.proxy.WebSocket",
"devDependencies": {
"Faker": "~0.7.0",
- "ws": "~0.4.31",
- "load-grunt-tasks": "~0.2.1",
- "grunt-contrib-uglify": "~0.3.0",
+ "connect-livereload": "^0.4.0",
"grunt": "~0.4.2",
- "grunt-contrib-jshint": "~0.8.0"
+ "grunt-bower-verify": "^2.0.0",
+ "grunt-contrib-jshint": "~0.8.0",
+ "grunt-contrib-uglify": "~0.3.0",
+ "load-grunt-tasks": "~0.2.1",
+ "ws": "~0.4.31"
+ },
+ "architect": {
+ "compatFrameworks": [
+ "ext42"
+ ],
+ "classes": [
+ {
+ "definition": "WebSocketProxy.Definition.js",
+ "className": "Ext.ux.data.proxy.WebSocket",
+ "js": [
+ "WebSocketProxy.js"
+ ],
+ "css": []
+ },
+ {
+ "definition": "WebSocket.Definition.js",
+ "className": "Ext.ux.WebSocket",
+ "js": [
+ "WebSocketManager.js",
+ "WebSocket.js"
+ ],
+ "css": []
+ }
+ ]
}
}
diff --git a/src/Readme.md b/src/Readme.md
new file mode 100644
index 0000000..cf8f142
--- /dev/null
+++ b/src/Readme.md
@@ -0,0 +1,4 @@
+# ext.ux.data.proxy.websocket/src
+
+This folder contains source code that will automatically be added to the classpath when
+the package is used.
diff --git a/src/WebSocket.js b/src/WebSocket.js
new file mode 100644
index 0000000..4f6478e
--- /dev/null
+++ b/src/WebSocket.js
@@ -0,0 +1,552 @@
+/**
+ * @class Ext.ux.WebSocket
+ * @author Vincenzo Ferrari
+ *
+ * Wrapper for HTML5 WebSocket
+ *
+ * This class provide an interface for HTML5 WebSocket.
+ *
+ * Pure text communication
+ * The communication is text-only, without objects or any other kind of data.
+ *
+ * var websocket = Ext.create ('Ext.ux.WebSocket', {
+ * url: 'ws://localhost:8888' ,
+ * listeners: {
+ * open: function (ws) {
+ * console.log ('The websocket is ready to use');
+ * ws.send ('This is a simple text');
+ * } ,
+ * close: function (ws) {
+ * console.log ('The websocket is closed!');
+ * } ,
+ * error: function (ws, error) {
+ * Ext.Error.raise (error);
+ * } ,
+ * message: function (ws, message) {
+ * console.log ('A new message is arrived: ' + message);
+ * }
+ * }
+ * });
+ *
+ * Pure event-driven communication
+ * The communication is event-driven: an event and a String or Object are sent and the websocket handles different events.
+ *
+ * var websocket = Ext.create ('Ext.ux.WebSocket', {
+ * url: 'ws://localhost:8888' ,
+ * listeners: {
+ * open: function (ws) {
+ * console.log ('The websocket is ready to use');
+ * ws.send ('init', 'This is a simple text');
+ * ws.send ('and continue', {
+ * 'my': 'data' ,
+ * 'your': 'data'
+ * });
+ * } ,
+ * close: function (ws) {
+ * console.log ('The websocket is closed!');
+ * }
+ * }
+ * });
+ *
+ * // A 'stop' event is sent from the server
+ * // 'data' has 'cmd' and 'msg' fields
+ * websocket.on ('stop', function (data) {
+ * console.log ('Command: ' + data.cmd);
+ * console.log ('Message: ' + data.msg);
+ * });
+ *
+ * Mixed event-driven and text communication
+ * The communication is mixed: it can handles text-only and event-driven communication.
+ *
+ * var websocket = Ext.create ('Ext.ux.WebSocket', {
+ * url: 'ws://localhost:8888' ,
+ * listeners: {
+ * open: function (ws) {
+ * console.log ('The websocket is ready to use');
+ * ws.send ('This is only-text message');
+ * ws.send ('init', 'This is a simple text');
+ * ws.send ('and continue', {
+ * 'my': 'data' ,
+ * 'your': 'data'
+ * });
+ * } ,
+ * close: function (ws) {
+ * console.log ('The websocket is closed!');
+ * } ,
+ * message: function (ws, message) {
+ * console.log ('Text-only message arrived is: ' + message);
+ * }
+ * }
+ * });
+ *
+ * // A 'stop' event is sent from the server
+ * // 'data' has 'cmd' and 'msg' fields
+ * websocket.on ('stop', function (data) {
+ * console.log ('Command: ' + data.cmd);
+ * console.log ('Message: ' + data.msg);
+ * });
+ */
+
+Ext.define ('Ext.ux.WebSocket', {
+ alias: 'widget.websocket' ,
+
+ mixins: {
+ observable: 'Ext.util.Observable'
+ } ,
+
+ requires: ['Ext.util.TaskManager', 'Ext.util.Memento'] ,
+
+ config: {
+ /**
+ * @cfg {String} url (required) The URL to connect
+ */
+ url: '' ,
+
+ /**
+ * @cfg {String} protocol The protocol to use in the connection
+ */
+ protocol: null ,
+
+ /**
+ * @cfg {String} communicationType The type of communication. 'both' (default) for event-driven and pure-text communication, 'event' for only event-driven and 'text' for only pure-text.
+ */
+ communicationType: 'both' ,
+
+ /**
+ * @cfg {Boolean} autoReconnect If the connection is closed by the server, it tries to re-connect again. The execution interval time of this operation is specified in autoReconnectInterval
+ */
+ autoReconnect: true ,
+
+ /**
+ * @cfg {Int} autoReconnectInterval Execution time slice of the autoReconnect operation, specified in milliseconds.
+ */
+ autoReconnectInterval: 5000 ,
+
+ /**
+ * @cfg {Boolean} lazyConnection Connect the websocket after the initialization with the open method
+ */
+ lazyConnection: false ,
+
+ /**
+ * @cfg {Boolean} keepUnsentMessages Keep unsent messages and try to send them back after the connection is open again
+ */
+ keepUnsentMessages: false
+ } ,
+
+ /**
+ * @property {Number} CONNECTING
+ * @readonly
+ * The connection is not yet open.
+ */
+ CONNECTING: 0 ,
+
+ /**
+ * @property {Number} OPEN
+ * @readonly
+ * The connection is open and ready to communicate.
+ */
+ OPEN: 1 ,
+
+ /**
+ * @property {Number} CLOSING
+ * @readonly
+ * The connection is in the process of closing.
+ */
+ CLOSING: 2 ,
+
+ /**
+ * @property {Number} CLOSED
+ * @readonly
+ * The connection is closed or couldn't be opened.
+ */
+ CLOSED: 3 ,
+
+ /**
+ * @property {Object} memento
+ * @private
+ * Internal memento
+ */
+ memento: {} ,
+
+ /**
+ * @property {Array} memento
+ * @private
+ * Internal queue of unsent messages
+ */
+ messageQueue: [] ,
+
+ /**
+ * Creates new WebSocket
+ * @param {String/Object} config The configuration options may be specified as follows:
+ *
+ * // with a configuration set
+ * var config = {
+ * url: 'your_url' ,
+ * protocol: 'your_protocol'
+ * };
+ *
+ * var ws = Ext.create ('Ext.ux.WebSocket', config);
+ *
+ * // or with websocket url only
+ * var ws = Ext.create ('Ext.ux.WebSocket', 'ws://localhost:30000');
+ *
+ * @return {Ext.ux.WebSocket} An instance of Ext.ux.WebSocket or null if an error occurred.
+ */
+ constructor: function (cfg) {
+ var me = this;
+
+ // Raises an error if no url is given
+ if (Ext.isEmpty (cfg)) {
+ Ext.Error.raise ('URL for the websocket is required!');
+ return null;
+ }
+
+ // Allows initialization with string
+ // e.g.: Ext.create ('Ext.ux.WebSocket', 'ws://localhost:8888');
+ if (typeof cfg === 'string') {
+ cfg = {
+ url: cfg
+ };
+ }
+
+ me.initConfig (cfg);
+ me.mixins.observable.constructor.call (me, cfg);
+
+ me.addEvents (
+ /**
+ * @event open
+ * Fires after the websocket has been connected.
+ * @param {Ext.ux.WebSocket} this The websocket
+ */
+ 'open' ,
+
+ /**
+ * @event error
+ * Fires after an error occured
+ * @param {Ext.ux.WebSocket} this The websocket
+ * @param {Object} error The error object to display
+ */
+ 'error' ,
+
+ /**
+ * @event close
+ * Fires after the websocket has been disconnected.
+ * @param {Ext.ux.WebSocket} this The websocket
+ */
+ 'close' ,
+
+ /**
+ * @event message
+ * Fires after a message is arrived from the server.
+ * @param {Ext.ux.WebSocket} this The websocket
+ * @param {String/Object} message The message arrived
+ */
+ 'message'
+ );
+
+ try {
+ // Initializes internal websocket
+ if (!me.getLazyConnection ()) me.initWebsocket ();
+
+ me.memento = Ext.create ('Ext.util.Memento');
+ me.memento.capture ('autoReconnect', me);
+ }
+ catch (err) {
+ Ext.Error.raise (err);
+ return null;
+ }
+
+ return me;
+ } ,
+
+ /**
+ * @method isReady
+ * Returns if the websocket connection is up or not
+ * @return {Boolean} True if the connection is up, False otherwise
+ */
+ isReady: function () {
+ return this.getStatus () === this.OPEN;
+ } ,
+
+ /**
+ * @method getStatus
+ * Returns the current status of the websocket
+ * @return {Number} The current status of the websocket (0: connecting, 1: open, 2: closed)
+ */
+ getStatus: function () {
+ return this.ws.readyState;
+ } ,
+
+ /**
+ * @method close
+ * Closes the websocket and kills the autoreconnect task, if exists
+ * @return {Ext.ux.WebSocket} The websocket
+ */
+ close: function () {
+ var me = this;
+
+ if (me.autoReconnectTask) {
+ Ext.TaskManager.stop (me.autoReconnectTask);
+ delete me.autoReconnectTask;
+ }
+ // Deactivate autoReconnect until the websocket is open again
+ me.setAutoReconnect (false);
+
+ me.ws.close ();
+
+ return me;
+ } ,
+
+ /**
+ * @method open
+ * Re/Open the websocket
+ * @return {Ext.ux.WebSocket} The websocket
+ */
+ open: function () {
+ var me = this;
+
+ // Restore autoReconnect initial value
+ me.memento.restore ('autoReconnect', false, me);
+ me.initWebsocket ();
+
+ return me;
+ } ,
+
+ /**
+ * @method send
+ * Sends a message.
+ * This method is bind at run-time level because it changes on the websocket initial configuration.
+ * It supports three kind of communication:
+ *
+ * 1. text-only
+ * Syntax: ws.send (string);
+ * Example: ws.send ('hello world!');
+ * 2. event-driven
+ * Syntax: ws.send (event, string/object);
+ * Example 1: ws.send ('greetings', 'hello world!');
+ * Example 2: ws.send ('greetings', {text: 'hello world!'});
+ * 3. hybrid (text and event)
+ * It uses both: see examples above
+ * @param {String/Object} message Can be a single text message or an association of event/message.
+ */
+ send: function () {} ,
+
+ /**
+ * @method initWebsocket
+ * Internal websocket initialization
+ * @private
+ */
+ initWebsocket: function () {
+ var me = this;
+
+ me.ws = Ext.isEmpty (me.getProtocol ()) ? new WebSocket (me.getUrl ()) : new WebSocket (me.getUrl (), me.getProtocol ());
+
+ me.ws.onopen = function (evt) {
+ // Kills the auto reconnect task
+ // It will be reactivated at the next onclose event
+ if (me.autoReconnectTask) {
+ Ext.TaskManager.stop (me.autoReconnectTask);
+ delete me.autoReconnectTask;
+ }
+
+ // Flush unset messages
+ if (me.getKeepUnsentMessages () && me.messageQueue.length > 0) {
+ while (me.messageQueue.length > 0) {
+ // Avoid infinite loop into safeSend method
+ if (me.isReady ()) me.safeSend (me.messageQueue.shift ());
+ else break;
+ }
+ }
+
+ me.fireEvent ('open', me);
+ };
+
+ me.ws.onerror = function (error) {
+ me.fireEvent ('error', me, error);
+ };
+
+ me.ws.onclose = function (evt) {
+ me.fireEvent ('close', me);
+
+ // Setups the auto reconnect task, just one
+ if (me.getAutoReconnect () && (typeof me.autoReconnectTask === 'undefined')) {
+ me.autoReconnectTask = Ext.TaskManager.start ({
+ run: function () {
+ // It reconnects only if it's disconnected
+ if (me.getStatus () === me.CLOSED) {
+ me.initWebsocket ();
+ }
+ } ,
+ interval: me.getAutoReconnectInterval ()
+ });
+ }
+ };
+
+ if (me.getCommunicationType () === 'both') {
+ me.ws.onmessage = Ext.bind (me.receiveBothMessage, this);
+ me.send = Ext.bind (me.sendBothMessage, this);
+ }
+ else if (me.getCommunicationType () === 'event') {
+ me.ws.onmessage = Ext.bind (me.receiveEventMessage, this);
+ me.send = Ext.bind (me.sendEventMessage, this);
+ }
+ else {
+ me.ws.onmessage = Ext.bind (me.receiveTextMessage, this);
+ me.send = Ext.bind (me.sendTextMessage, this);
+ }
+ } ,
+
+ /**
+ * @method flush
+ * It sends every message given to the websocket, checking first if is there any connection
+ * If there's no connection, it enqueues the message and flushes it later
+ * @param {String} Data to send
+ * @return {Ext.ux.WebSocket} The websocket
+ * @private
+ */
+ safeSend: function (data) {
+ var me = this;
+
+ if (me.isReady ()) me.ws.send (data);
+ else if (me.getKeepUnsentMessages ()) me.messageQueue.push (data);
+
+ return me;
+ } ,
+
+ /**
+ * @method receiveBothMessage
+ * It catches every event-driven and pure text messages incoming from the server
+ * @param {Object} message Message incoming from the server
+ * @private
+ */
+ receiveBothMessage: function (message) {
+ var me = this;
+
+ try {
+ /*
+ message.data : JSON encoded message
+ msg.event : event to be raise
+ msg.data : data to be handle
+ */
+ var msg = Ext.JSON.decode (message.data);
+ me.fireEvent (msg.event, me, msg.data);
+ me.fireEvent ('message', me, msg);
+ }
+ catch (err) {
+ if (Ext.isString (message.data)) me.fireEvent (message.data, me, message.data);
+ // Message event is always sent
+ me.fireEvent ('message', me, message.data);
+ }
+ } ,
+
+ /**
+ * @method receiveEventMessage
+ * It catches every event-driven messages incoming from the server
+ * @param {Object} message Message incoming from the server
+ * @private
+ */
+ receiveEventMessage: function (message) {
+ var me = this;
+
+ try {
+ var msg = Ext.JSON.decode (message.data);
+ me.fireEvent (msg.event, me, msg.data);
+ me.fireEvent ('message', me, msg);
+ }
+ catch (err) {
+ Ext.Error.raise (err);
+ }
+ } ,
+
+ /**
+ * @method receiveTextMessage
+ * It catches every pure text messages incoming from the server
+ * @param {Object} message Message incoming from the server
+ * @private
+ */
+ receiveTextMessage: function (message) {
+ var me = this;
+
+ try {
+ me.fireEvent (message, me, message);
+ // Message event is always sent
+ me.fireEvent ('message', me, message);
+ }
+ catch (err) {
+ Ext.Error.raise (err);
+ }
+ } ,
+
+ /**
+ * @method sendBothMessage
+ * It sends both pure text and event-driven messages to the server
+ * @param {String/String[]} events Message(s) or event(s) to send to the server
+ * @param {String/Object} data Message to send to the server, associated to its event
+ * @return {Ext.ux.WebSocket} The websocket
+ * @private
+ */
+ sendBothMessage: function (events, data) {
+ var me = this;
+
+ // Treats it as normal message
+ if (arguments.length === 1) {
+ if (Ext.isString (events)) me.safeSend (events);
+ else Ext.Error.raise ('String expected!');
+ }
+ // Treats it as event-driven message
+ else if (arguments.length >= 2) {
+ events = Ext.isString (events) ? [events] : events;
+
+ for (var i=0; i
+ * @singleton
+ *
+ * Manager of Ext.ux.WebSocket
+ *
+ * This singleton provide some useful functions to use for many websockets.
+ *
+ * var ws1 = Ext.create ('Ext.ux.WebSocket', {
+ * url: 'ws://localhost:8888'
+ * });
+ *
+ * Ext.ux.WebSocketManager.register (ws1);
+ *
+ * var ws2 = Ext.create ('Ext.ux.WebSocket', {
+ * url: 'ws://localhost:8900'
+ * });
+ *
+ * Ext.ux.WebSocketManager.register (ws2);
+ *
+ * var ws3 = Ext.create ('Ext.ux.WebSocket', {
+ * url: 'ws://localhost:8950'
+ * });
+ *
+ * Ext.ux.WebSocketManager.register (ws3);
+ *
+ * Ext.ux.WebSocketManager.listen ('system shutdown', function (ws, data) {
+ * Ext.Msg.show ({
+ * title: 'System Shutdown' ,
+ * msg: data ,
+ * icon: Ext.Msg.WARNING ,
+ * buttons: Ext.Msg.OK
+ * });
+ * });
+ *
+ * Ext.ux.WebSocketManager.broadcast ('system shutdown', 'BROADCAST: the system will shutdown in few minutes.');
+ *
+ * Ext.ux.WebSocketManager.closeAll ();
+ *
+ * Ext.ux.WebSocketManager.unregister (ws1);
+ * Ext.ux.WebSocketManager.unregister (ws2);
+ * Ext.ux.WebSocketManager.unregister (ws3);
+ */
+Ext.define ('Ext.ux.WebSocketManager', {
+ singleton: true ,
+
+ /**
+ * @property {Ext.util.HashMap} wsList
+ * @private
+ */
+ wsList: Ext.create ('Ext.util.HashMap') ,
+
+ /**
+ * @method register
+ * Registers one or more Ext.ux.WebSocket
+ * @param {Ext.ux.WebSocket/Ext.ux.WebSocket[]} websockets WebSockets to register. Could be only one.
+ */
+ register: function (websockets) {
+ var me = this;
+
+ // Changes websockets into an array in every case
+ if (Ext.isObject (websockets)) websockets = [websockets];
+
+ Ext.each (websockets, function (websocket) {
+ if (!Ext.isEmpty (websocket.url)) me.wsList.add (websocket.url, websocket);
+ });
+ } ,
+
+ /**
+ * @method contains
+ * Checks if a websocket is already registered or not
+ * @param {Ext.ux.WebSocket} websocket The WebSocket to find
+ * @return {Boolean} True if the websocket is already registered, False otherwise
+ */
+ contains: function (websocket) {
+ return this.wsList.containsKey (websocket.url);
+ } ,
+
+ /**
+ * @method get
+ * Retrieves a registered websocket by its url
+ * @param {String} url The url of the websocket to search
+ * @return {Ext.ux.WebSocket} The websocket or undefined
+ */
+ get: function (url) {
+ return this.wsList.get (url);
+ } ,
+
+ /**
+ * @method each
+ * Executes a function for each registered websocket
+ * @param {Function} fn The function to execute
+ */
+ each: function (fn) {
+ this.wsList.each (function (url, websocket, len) {
+ fn (websocket);
+ });
+ } ,
+
+ /**
+ * @method unregister
+ * Unregisters one or more Ext.ux.WebSocket
+ * @param {Ext.ux.WebSocket/Ext.ux.WebSocket[]} websockets WebSockets to unregister
+ */
+ unregister: function (websockets) {
+ var me = this;
+
+ if (Ext.isObject (websockets)) websockets = [websockets];
+
+ Ext.each (websockets, function (websocket) {
+ if (me.wsList.containsKey (websocket.url)) me.wsList.removeAtKey (websocket.url);
+ });
+ } ,
+
+ /**
+ * @method broadcast
+ * Sends a message to each websocket
+ * @param {String} event The event to raise
+ * @param {String/Object} message The data to send
+ */
+ broadcast: function (event, message) {
+ this.multicast ([], event, message);
+ } ,
+
+ /**
+ * @method multicast
+ * Sends a message to each websocket, except those specified
+ * @param {Ext.ux.WebSocket/Ext.ux.WebSocket[]} websockets An array of websockets to take off the communication
+ * @param {String} event The event to raise
+ * @param {String/Object} data The data to send
+ */
+ multicast: function (websockets, event, data) {
+ this.getExcept(websockets).each (function (url, websocket, len) {
+ if (websocket.isReady ()) {
+ if (Ext.isEmpty (data)) websocket.send (event);
+ else websocket.send (event, data);
+ }
+ });
+ } ,
+
+ /**
+ * @method listen
+ * Adds an handler for events given to each registered websocket
+ * @param {String/String[]} events Events to listen
+ * @param {Function} handler The events' handler
+ */
+ listen: function (events, handler) {
+ if (Ext.isString (events)) events = [events];
+
+ this.wsList.each (function (url, websocket, len) {
+ Ext.each (events, function (event) {
+ websocket.on (event, handler);
+ });
+ });
+ } ,
+
+ /**
+ * @method listenExcept
+ * Adds an handler for events given to each registered websocket, except websockets given
+ * @param {String/String[]} events Events to listen
+ * @param {Ext.ux.WebSocket/Ext.ux.WebSocket[]} websockets WebSockets to exclude
+ * @param {Function} handler The events' handler
+ */
+ listenExcept: function (events, websockets, handler) {
+ if (Ext.isString (events)) events = [events];
+
+ this.getExcept(websockets).each (function (url, websocket, len) {
+ Ext.each (events, function (event) {
+ websocket.on (event, handler);
+ });
+ });
+ } ,
+
+ /**
+ * @method getExcept
+ * Retrieves registered websockets except the input
+ * @param {Ext.ux.WebSocket/Ext.ux.WebSocket[]} websockets WebSockets to exclude
+ * @return {Ext.util.HashMap} Registered websockets except the input
+ * @private
+ */
+ getExcept: function (websockets) {
+ if (Ext.isObject (websockets)) websockets = [websockets];
+
+ var list = this.wsList.clone ();
+
+ // Exclude websockets from the communication
+ Ext.each (websockets, function (websocket) {
+ list.removeAtKey (websocket.url);
+ });
+
+ return list;
+ } ,
+
+ /**
+ * @method closeAll
+ * Closes any registered websocket
+ */
+ closeAll: function () {
+ var me = this;
+
+ me.wsList.each (function (url, websocket, len) {
+ websocket.close ();
+ me.unregister (websocket);
+ });
+ }
+});
\ No newline at end of file
diff --git a/WebSocket.js b/src/WebSocketProxy.js
similarity index 100%
rename from WebSocket.js
rename to src/WebSocketProxy.js