├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── README.th.md ├── css ├── freeboard.css ├── freeboard.min.css ├── netpie.plugins.css └── netpie.theme.css ├── docs ├── build-docs.sh ├── docco-fb.css ├── docco.css ├── plugin_example.html └── public │ ├── fonts │ ├── aller-bold.eot │ ├── aller-bold.ttf │ ├── aller-bold.woff │ ├── aller-light.eot │ ├── aller-light.ttf │ ├── aller-light.woff │ ├── fleurons.eot │ ├── fleurons.ttf │ ├── fleurons.woff │ ├── novecento-bold.eot │ ├── novecento-bold.ttf │ └── novecento-bold.woff │ ├── images │ └── gray.png │ └── stylesheets │ └── normalize.css ├── examples ├── altGuage.js ├── plugin_example.js ├── rl78.json └── weather.json ├── img ├── dropdown-arrow-black.png ├── dropdown-arrow.png ├── glyphicons-blackboard.png ├── glyphicons-halflings-grey.png ├── glyphicons-halflings-white.png ├── glyphicons-halflings.png ├── glyphicons-log-in.png ├── glyphicons-log-out.png ├── moon.png ├── netpie-logo-black.png ├── netpie-logo-white.png └── sun.png ├── index-dev.html ├── index.html ├── js ├── freeboard.js ├── freeboard.plugins.js ├── freeboard.thirdparty.js ├── freeboard_plugins.js ├── netpie.feedview.js ├── netpie.global.js ├── netpie.js.zip ├── netpie.plugin.feed.js ├── netpie.plugin.microgear.js ├── netpie.widget.button.js ├── netpie.widget.feedview.js ├── netpie.widget.slider.js └── netpie.widget.toggle.js ├── lib ├── css │ ├── freeboard │ │ └── styles.css │ └── thirdparty │ │ ├── codemirror-ambiance.css │ │ ├── codemirror.css │ │ └── jquery.gridster.min.css └── js │ ├── freeboard │ ├── DatasourceModel.js │ ├── DeveloperConsole.js │ ├── DialogBox.js │ ├── FreeboardModel.js │ ├── FreeboardUI.js │ ├── JSEditor.js │ ├── PaneModel.js │ ├── PluginEditor.js │ ├── ValueEditor.js │ ├── WidgetModel.js │ └── freeboard.js │ └── thirdparty │ ├── codemirror.js │ ├── head.js │ ├── jquery-ui.js │ ├── jquery.caret.js │ ├── jquery.gridster.js │ ├── jquery.js │ ├── jquery.xdomainrequest.js │ ├── knockout.js │ └── underscore.js ├── package.json ├── plugins ├── freeboard │ ├── freeboard.datasources.js │ └── freeboard.widgets.js └── thirdparty │ ├── jquery.sparkline.min.js │ ├── justgage.1.0.1.js │ ├── justgage.1.2.2.js │ ├── rangeslider.css │ ├── rangeslider.js │ ├── raphael.2.1.0.min.js │ └── raphael.2.1.4.min.js └── test ├── casper ├── tests │ └── smoke_test.js └── util │ └── test_util.js ├── fixtures └── input.json └── run_browser_tests.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | concat: { 5 | css: { 6 | src: [ 7 | 'lib/css/thirdparty/*.css', 8 | 'lib/css/freeboard/styles.css' 9 | ], 10 | dest: 'css/freeboard.css' 11 | }, 12 | thirdparty : { 13 | src : [ 14 | [ 15 | 'lib/js/thirdparty/head.js', 16 | 'lib/js/thirdparty/jquery.js', 17 | 'lib/js/thirdparty/jquery-ui.js', 18 | 'lib/js/thirdparty/knockout.js', 19 | 'lib/js/thirdparty/underscore.js', 20 | 'lib/js/thirdparty/jquery.gridster.js', 21 | 'lib/js/thirdparty/jquery.caret.js', 22 | 'lib/js/thirdparty/jquery.xdomainrequest.js', 23 | 'lib/js/thirdparty/codemirror.js', 24 | ] 25 | ], 26 | dest : 'js/freeboard.thirdparty.js' 27 | }, 28 | fb : { 29 | src : [ 30 | 'lib/js/freeboard/DatasourceModel.js', 31 | 'lib/js/freeboard/DeveloperConsole.js', 32 | 'lib/js/freeboard/DialogBox.js', 33 | 'lib/js/freeboard/FreeboardModel.js', 34 | 'lib/js/freeboard/FreeboardUI.js', 35 | 'lib/js/freeboard/JSEditor.js', 36 | 'lib/js/freeboard/PaneModel.js', 37 | 'lib/js/freeboard/PluginEditor.js', 38 | 'lib/js/freeboard/ValueEditor.js', 39 | 'lib/js/freeboard/WidgetModel.js', 40 | 'lib/js/freeboard/freeboard.js', 41 | ], 42 | dest : 'js/freeboard.js' 43 | }, 44 | plugins : { 45 | src : [ 46 | 'plugins/freeboard/*.js' 47 | ], 48 | dest : 'js/freeboard.plugins.js' 49 | }, 50 | 'fb_plugins' : { 51 | src : [ 52 | 'js/freeboard.js', 53 | 'js/freeboard.plugins.js' 54 | ], 55 | dest : 'js/freeboard_plugins.js' 56 | } 57 | }, 58 | cssmin : { 59 | css:{ 60 | src: 'css/freeboard.css', 61 | dest: 'css/freeboard.min.css' 62 | } 63 | }, 64 | uglify : { 65 | fb: { 66 | files: { 67 | 'js/freeboard.min.js' : [ 'js/freeboard.js' ] 68 | } 69 | }, 70 | plugins: { 71 | files: { 72 | 'js/freeboard.plugins.min.js' : [ 'js/freeboard.plugins.js' ] 73 | } 74 | }, 75 | thirdparty :{ 76 | options: { 77 | mangle : false, 78 | beautify : false, 79 | compress: {} 80 | }, 81 | files: { 82 | 'js/freeboard.thirdparty.min.js' : [ 'js/freeboard.thirdparty.js' ] 83 | } 84 | }, 85 | 'fb_plugins': { 86 | files: { 87 | 'js/freeboard_plugins.min.js' : [ 'js/freeboard_plugins.js' ] 88 | } 89 | } 90 | }, 91 | 'string-replace': { 92 | css: { 93 | files: { 94 | 'css/': 'css/*.css' 95 | }, 96 | options: { 97 | replacements: [{ 98 | pattern: /..\/..\/..\/img/ig, 99 | replacement: '../img' 100 | }] 101 | } 102 | } 103 | } 104 | }); 105 | 106 | grunt.loadNpmTasks('grunt-contrib-concat'); 107 | grunt.loadNpmTasks('grunt-contrib-uglify'); 108 | grunt.loadNpmTasks('grunt-contrib-watch'); 109 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 110 | grunt.loadNpmTasks('grunt-string-replace'); 111 | grunt.registerTask('default', [ 'concat:css', 'cssmin:css', 'concat:fb', 'concat:thirdparty', 'concat:plugins', 'concat:fb_plugins', 'uglify:fb', 'uglify:plugins', 'uglify:fb_plugins', 'uglify:thirdparty', 'string-replace:css' ]); 112 | }; 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Jim Heising and Bug Labs, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | netpie-freeboard 2 | ========== 3 | 4 | A freeboard with NETPIE datasource and widget plugins. 5 | 6 | ![netpie-freeboard-screenshot](https://cloud.githubusercontent.com/assets/7685964/19427706/de8aab54-946f-11e6-81ae-bbe8b78910e5.jpg) 7 | 8 | 9 | Installation 10 | 11 | git clone https://github.com/netpieio/netpie-freeboard 12 | 13 | To use a freeboard, just open the file index.html in any web browser. Under the DATASOURCE section, click ADD to create a new datasource then select NETPIE Microgear and configure as follows: 14 | 15 | - **NAME** - This is the name of the datasource. It also holds a microgear object that is referenced by microgear[*NAME*]. Moreover this name will be registered as a microgear device alias that you can chat to. 16 | - **APP ID** - NETPIE App ID 17 | - **KEY** - Microgear Key 18 | - **SECRET** - Secret of the key 19 | - **DEVICE ALIAS** - If needed you can name the datasource as a microgear so it can be reached by a function chat() 20 | - **SUBSCRIBE TOPICS** - Topics that this datasource will subscribe. Wild cards # and + are allowed. The default value is /# meaning that it subscribes to all topics in this App ID. 21 | 22 | ![netpie-freeboard2](https://cloud.githubusercontent.com/assets/7685964/15654634/fbe3c096-26bf-11e6-8ab5-4656839b53ad.jpg) 23 | 24 | As for the button widget, you can configure it to execute Javascipt codes upon the onClick event. In the picture below the button is configured to send a chat message to the device named *pieslampher* everytime it is clicked. The index 'mg1' is simply the reference of a microgear of a datasource *netpie1* you entered in the datasource configurtion. 25 | 26 | ![netpie-freeboard3](https://cloud.githubusercontent.com/assets/7685964/15655823/ec23a1f2-26ca-11e6-9968-ee500136b7bc.jpg) 27 | -------------------------------------------------------------------------------- /README.th.md: -------------------------------------------------------------------------------- 1 | netpie-freeboard 2 | ========== 3 | 4 | Freeboard ที่มาพร้อม NETPIE datasource and widget plugins 5 | 6 | ![netpie-freeboard-screenshot](https://cloud.githubusercontent.com/assets/7685964/19427706/de8aab54-946f-11e6-81ae-bbe8b78910e5.jpg) 7 | 8 | 9 | วิธีติดตั้ง 10 | 11 | git clone https://github.com/netpieio/netpie-freeboard 12 | 13 | การใช้งาน ใช้ browser เปิดไฟล์ index.html ในส่วนของ DATASOURCES คลิก ADD เลือก TYPE เป็น NETPIE Microgear ปรับแต่งค่าตามความเหมาะสม 14 | 15 | - **NAME** - ชื่อของ datasource ซึ่งแต่ละ NETPIE microgear datasource จะมี microgear object ที่เข้าถึงจาก script ได้ทาง microgear[*NAME*] นอกจากนั้น ชื่อนี้ยังใช้เป็น microgear device alias ที่ device อื่นสามารถ chat มาหาได้ 16 | - **APP ID** - App ID ของ NETPIE 17 | - **KEY** - Key ของ microgear 18 | - **SECRET - Secret** ของ key ข้างต้น 19 | - **SUBSCRIBE TOPICS** - เป็น topic ที่จะให้ datasource นี้ subscribe หากมีหลาย topic ให้ใช้ comma คั่น สามารถใช้ wildcard # และ + ได้ ค่าปกติจะเป็น /# คือ subscribe ทุก topic ของ App ID นี้ 20 | 21 | ![netpie-freeboard2](https://cloud.githubusercontent.com/assets/7685964/15654634/fbe3c096-26bf-11e6-8ab5-4656839b53ad.jpg) 22 | 23 | ในส่วนของ button widget เราสามารถปรับแต่งให้ปุ่ม มีการรันโค้ด Javascript เมื่อเกิดการกดได้ ตามตัวอย่างในรูป ปุ่มจะถูกกำหนดให้ส่ง chat message ไปยัง thing ที่ชื่อ pieslampher ทุกครั้งที่ถูกกด 24 | โดยที่ index 'mg1' คือ microgear reference ของ datasource netpie1 ที่เราได้กำหนดไว้ตอนสร้าง datasource 25 | 26 | ![netpie-freeboard3](https://cloud.githubusercontent.com/assets/7685964/15655823/ec23a1f2-26ca-11e6-9968-ee500136b7bc.jpg) 27 | -------------------------------------------------------------------------------- /css/netpie.plugins.css: -------------------------------------------------------------------------------- 1 | .netpie-button { 2 | height: 36px; 3 | width: 50%; 4 | display: inline-block; 5 | box-shadow: 0px 1px 0px 1px #111111; 6 | border-radius: 4px; 7 | text-decoration: none; 8 | text-align: center; 9 | outline: none; 10 | font-size: 125%; 11 | float: left; 12 | } 13 | 14 | .netpie-button-text { 15 | vertical-align: bottom; 16 | padding: 8px 0px 0px 15px; 17 | float: left; 18 | } 19 | 20 | .netpie-toggle { 21 | float: left; 22 | position: relative; 23 | width: 100px; 24 | margin-top: 6px; 25 | -webkit-user-select: none; 26 | -moz-user-select: none; 27 | -ms-user-select: none; 28 | } 29 | 30 | .netpie-toggle-text { 31 | vertical-align: top; 32 | padding: 15px 0px 0px 15px; 33 | float: left; 34 | } 35 | 36 | .netpie-toggle-checkbox { 37 | display: none; 38 | } 39 | 40 | .netpie-toggle-label { 41 | display: block; 42 | overflow: hidden; 43 | cursor: pointer; 44 | border: 2px solid #999999; 45 | border-radius: 20px; 46 | } 47 | 48 | .netpie-toggle-inner { 49 | display: block; 50 | width: 200%; 51 | margin-left: -100%; 52 | transition: margin 0.1s ease-in 0s; 53 | } 54 | 55 | .netpie-toggle-inner:before, .netpie-toggle-inner:after { 56 | display: block; 57 | float: left; 58 | width: 50%; 59 | height: 30px; 60 | padding: 0; 61 | line-height: 30px; 62 | font-size: 14px; 63 | color: white; 64 | font-family: Trebuchet, Arial, sans-serif; 65 | font-weight: bold; 66 | box-sizing: border-box; 67 | } 68 | 69 | .netpie-toggle-inner:before { 70 | content: attr(ontext); 71 | padding-left: 15px; 72 | background-color: #2ecc71; 73 | color: #FFFFFF; 74 | text-align: left; 75 | } 76 | 77 | .netpie-toggle-inner:after { 78 | content: attr(offtext); 79 | padding-right: 15px; 80 | background-color: #E74C3C; 81 | color: #FFFFFF; 82 | text-align: right; 83 | } 84 | 85 | .netpie-toggle-switch { 86 | display: block; 87 | width: 18px; 88 | margin: 6px; 89 | background: #FFFFFF; 90 | position: absolute; 91 | top: 0; 92 | bottom: 0; 93 | right: 66px; 94 | border: 2px solid #999999; 95 | border-radius: 20px; 96 | transition: all 0.1s ease-in 0s; 97 | } 98 | 99 | .netpie-toggle-checkbox:checked + .netpie-toggle-label .netpie-toggle-inner { 100 | margin-left: 0; 101 | } 102 | 103 | .netpie-toggle-checkbox:checked + .netpie-toggle-label .netpie-toggle-switch { 104 | right: 0px; 105 | } 106 | 107 | .netpit-feedvew-header { 108 | display: inline-block, vertical-align: middle, width: 100%, height: 10%, margin: auto, text-align: center, font: 14px/0.8em "proxima-nova", Helvetica, Arial, sans-serif, color: black, font-weight: bold 109 | } 110 | 111 | .logo-netpie { 112 | content: url("../img/netpie-logo-white.png"); 113 | } 114 | 115 | .theme-toggle { 116 | /*float: left;*/ 117 | position: absolute; 118 | top: 18px; 119 | right: 1vw; 120 | width: 50px; 121 | margin: auto; 122 | -webkit-user-select: none; 123 | -moz-user-select: none; 124 | -ms-user-select: none; 125 | } 126 | 127 | .theme-toggle-text { 128 | vertical-align: top; 129 | padding: 15px 0px 0px 15px; 130 | float: left; 131 | } 132 | 133 | .theme-toggle-checkbox { 134 | display: none; 135 | } 136 | 137 | .theme-toggle-inner { 138 | display: block; 139 | width: 200%; 140 | margin-left: -100%; 141 | transition: margin 0.1s ease-in 0s; 142 | } 143 | 144 | .theme-toggle-inner:before, .theme-toggle-inner:after { 145 | display: block; 146 | float: left; 147 | width: 50%; 148 | height: 20px; 149 | padding: 0; 150 | line-height: 30px; 151 | font-size: 14px; 152 | color: white; 153 | font-family: Trebuchet, Arial, sans-serif; 154 | font-weight: bold; 155 | box-sizing: border-box; 156 | } 157 | 158 | .theme-toggle-inner:before { 159 | content: url("../img/sun.png"); 160 | padding-left: 8px; 161 | background-color: #007FFF; 162 | color: #FFFFFF; 163 | text-align: left; 164 | } 165 | 166 | .theme-toggle-inner:after { 167 | content: url("../img/moon.png"); 168 | padding-right: 3px; 169 | background-color: #313131; 170 | color: #FFFFFF; 171 | text-align: right; 172 | } 173 | 174 | 175 | .theme-toggle-checkbox:checked + .theme-toggle-label .theme-toggle-inner { 176 | margin-left: 0; 177 | } 178 | 179 | .theme-toggle-checkbox:checked + .theme-toggle-label .theme-toggle-switch { 180 | right: 0px; 181 | } 182 | 183 | .theme-toggle-switch { 184 | display: block; 185 | width: 12px; 186 | height: 12px; 187 | margin: 4px; 188 | background: #FFFFFF; 189 | position: absolute; 190 | top: 0; 191 | bottom: 0; 192 | right: 25px; 193 | border: 2px solid #999999; 194 | border-radius: 20px; 195 | transition: all 0.1s ease-in 0s; 196 | } 197 | 198 | .theme-toggle-label { 199 | display: block; 200 | overflow: hidden; 201 | cursor: pointer; 202 | border: 2px solid #999999; 203 | border-radius: 20px; 204 | } 205 | 206 | .bg-graph { 207 | background-color: #2A2A2A; 208 | } 209 | 210 | .bg-graph div table tbody tr td { 211 | /*color: #B3B3B3 !important ;*/ 212 | color: #fff !important ; 213 | font-family: Trebuchet, Arial, sans-serif !important; 214 | font-weight: normal !important; 215 | } 216 | 217 | .bg-graph div .oneyaxis { 218 | color: #fff !important ; 219 | /*font-family: Trebuchet, Arial, sans-serif !important; 220 | font-weight: normal !important;*/ 221 | } 222 | 223 | .bg-graph div { 224 | /*color: #fff !important ;*/ 225 | font-family: Trebuchet, Arial, sans-serif !important; 226 | font-weight: normal !important; 227 | } 228 | 229 | .flot-x-axis div { 230 | color: #fff !important ; 231 | } 232 | 233 | .header_graph { 234 | color: #fff !important ; 235 | } 236 | 237 | .y_graph { 238 | color: #fff !important ; 239 | } 240 | 241 | .x_graph { 242 | color: #fff !important ; 243 | } 244 | 245 | .legendLabel{ 246 | font-size: 11px !important ; 247 | } -------------------------------------------------------------------------------- /css/netpie.theme.css: -------------------------------------------------------------------------------- 1 | /*css netpie theme*/ 2 | 3 | #admin-bar { 4 | background-color: #007FFF; 5 | } 6 | 7 | body { 8 | background-color: #ECF0F5; 9 | color: #262626; 10 | } 11 | 12 | #toggle-header { 13 | background-color: #007FFF; 14 | box-shadow: 0 0 1px #fff 15 | } 16 | 17 | .gridster .gs_w { 18 | background: white; 19 | } 20 | 21 | .gridster header { 22 | background-color: #007FFF; 23 | } 24 | 25 | .gridster header h1 { 26 | color: white; 27 | } 28 | 29 | .title { 30 | color: white; 31 | } 32 | 33 | .title.bordered { 34 | border: solid 3px white; 35 | } 36 | 37 | .board-toolbar li { 38 | color: #FED33C; 39 | /*background-color: #007FFF;*/ 40 | } 41 | 42 | .text-button { 43 | color: #FED33C; 44 | } 45 | 46 | a:hover.text-button, a:focus.text-button { 47 | color: #FED33C; 48 | } 49 | 50 | #modal_overlay { 51 | background: rgba(236, 240, 245, 0.6); 52 | } 53 | 54 | .modal { 55 | background-color: #007FFF; 56 | } 57 | 58 | .modal header { 59 | background-color: #3BA9E8; 60 | } 61 | 62 | .modal footer { 63 | background-color: #3BA9E8; 64 | } 65 | 66 | .styled-select select { 67 | border: 1px solid #3BA9E8; 68 | background: url(../img/dropdown-arrow-black.png) no-repeat right #ECF0F5; 69 | color: #262626; 70 | } 71 | 72 | .styled-select option{ 73 | border: 1px solid #007FFF; 74 | background-color: #3BA9E8; 75 | color: #262626; 76 | } 77 | 78 | textarea, input[type="text"], input[type="password"], input[type="datetime"], input[type="datetime-local"], input[type="date"], input[type="month"], input[type="time"], input[type="week"], input[type="number"], input[type="email"], input[type="url"], input[type="search"], input[type="tel"], input[type="color"], .uneditable-input { 79 | color: #262626; 80 | background-color: #ECF0F5; 81 | border: 1px solid #3BA9E8; 82 | } 83 | 84 | textarea:focus, input[type="text"]:focus, input[type="password"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="date"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, input[type="number"]:focus, input[type="email"]:focus, input[type="url"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="color"]:focus, .uneditable-input:focus { 85 | border-color: rgba(24, 56, 75, 0.6); 86 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgb(24, 56, 75); 87 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgb(24, 56, 75); 88 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgb(24, 56, 75); 89 | } 90 | 91 | select { 92 | width: 220px; 93 | background-color: #ECF0F5; 94 | height: 27px; 95 | } 96 | 97 | h1, h2, h3, h4, h5, h6 { 98 | color: #262626; 99 | } 100 | 101 | input::-webkit-input-placeholder, textarea::-webkit-input-placeholder { 102 | color: #307096; 103 | } 104 | 105 | input:-moz-placeholder, textarea:-moz-placeholder { 106 | color: #307096; 107 | } 108 | 109 | .board-toolbar li:hover { 110 | background-color: rgba( 59, 169, 232, 1.0); 111 | -webkit-transition: 250ms linear; 112 | -moz-transition: 250ms linear; 113 | -o-transition: 250ms linear; 114 | -ms-transition: 250ms linear; 115 | transition: 250ms linear; 116 | } 117 | 118 | .setting-description { 119 | color: #2e2e2e; 120 | } 121 | 122 | .bg-graph { 123 | background-color: #fff 124 | } 125 | 126 | ul.value-dropdown { 127 | height: 75px; 128 | position: absolute; 129 | padding: 0px; 130 | margin: 0px; 131 | border: 1px solid #3BA9E8; 132 | overflow-x: hidden; 133 | overflow-y: auto; 134 | z-index: 3001; 135 | background-color: #ECF0F5; 136 | color: #262626; 137 | } 138 | 139 | ul.value-dropdown li { 140 | padding: 5px; 141 | cursor: pointer; 142 | /*border: 1px solid #007FFF;*/ 143 | background-color: #3BA9E8; 144 | color: #262626; 145 | } 146 | 147 | ul.value-dropdown li.selected { 148 | border: 1px solid #007FFF; 149 | background-color: #1E90FF; 150 | color: #fff; 151 | } 152 | 153 | .onoffswitch-inner .on { 154 | background-color: #ECF0F5; 155 | color: #262626; 156 | } 157 | 158 | .onoffswitch-inner .off { 159 | background-color: #ECF0F5; 160 | color: #262626; 161 | } 162 | 163 | .onoffswitch-label { 164 | border: 1px solid #007FFF; 165 | } 166 | 167 | .table thead th { 168 | font-size: 11.5px; 169 | color: white; 170 | } 171 | 172 | .datasource-list-container { 173 | border-bottom: solid 1px #fff 174 | } 175 | .sub-section { 176 | border-bottom: solid 1px #ECF0F5; 177 | } 178 | .icon-grey, 179 | .nav-pills>.active>a>[class^=icon-], 180 | .nav-pills>.active>a>[class*=" icon-"], 181 | .nav-list>.active>a>[class^=icon-], 182 | .nav-list>.active>a>[class*=" icon-"], 183 | .navbar-inverse .nav>.active>a>[class^=icon-], 184 | .navbar-inverse .nav>.active>a>[class*=" icon-"], 185 | .dropdown-menu>li>a:hover>[class^=icon-], 186 | .dropdown-menu>li>a:focus>[class^=icon-], 187 | .dropdown-menu>li>a:hover>[class*=" icon-"], 188 | .dropdown-menu>li>a:focus>[class*=" icon-"], 189 | .dropdown-menu>.active>a>[class^=icon-], 190 | .dropdown-menu>.active>a>[class*=" icon-"], 191 | .dropdown-submenu:hover>a>[class^=icon-], 192 | .dropdown-submenu:focus>a>[class^=icon-], 193 | .dropdown-submenu:hover>a>[class*=" icon-"], 194 | .dropdown-submenu:focus>a>[class*=" icon-"] { 195 | background-image: url(../img/glyphicons-halflings-grey.png) 196 | } 197 | 198 | 199 | 200 | .theme-toggle-label { 201 | display: block; 202 | overflow: hidden; 203 | cursor: pointer; 204 | border: 2px solid #FFF; 205 | border-radius: 20px; 206 | } 207 | 208 | .theme-toggle-switch { 209 | display: block; 210 | width: 12px; 211 | height: 12px; 212 | margin: 4px; 213 | background: #FFFFFF; 214 | position: absolute; 215 | top: 0; 216 | bottom: 0; 217 | right: 25px; 218 | border: 2px solid #007FFF; 219 | border-radius: 20px; 220 | transition: all 0.1s ease-in 0s; 221 | } 222 | .gauge-widget svg text tspan { 223 | fill:#000; 224 | } 225 | 226 | .bg-graph div table tbody tr td { 227 | color: #000 !important ; 228 | font-family: Trebuchet, Arial, sans-serif !important; 229 | font-weight: normal !important; 230 | } 231 | 232 | .bg-graph div .oneyaxis { 233 | color: #000 !important ; 234 | /*font-family: Trebuchet, Arial, sans-serif !important; 235 | font-weight: normal !important;*/ 236 | } 237 | 238 | .bg-graph div { 239 | /*color: #000 !important ;*/ 240 | font-family: Trebuchet, Arial, sans-serif !important; 241 | font-weight: normal !important; 242 | } 243 | 244 | .flot-x-axis div { 245 | color: #000 !important ; 246 | } 247 | 248 | .header_graph { 249 | color: #000 !important ; 250 | } 251 | 252 | .y_graph { 253 | color: #000 !important ; 254 | } 255 | 256 | .x_graph { 257 | color: #000 !important ; 258 | } 259 | 260 | .indicator-light { 261 | background-color: #DCDCDC; 262 | border-color: #DCDCDC; 263 | } 264 | 265 | .indicator-light.on { 266 | border-color: #FFC773; 267 | } 268 | 269 | .gauge-widget svg path:nth-of-type(1) { 270 | fill: #DCDCDC; 271 | } 272 | 273 | .rangeSlider { 274 | background-color: #DCDCDC !important; 275 | } 276 | 277 | .tw-value { 278 | color: #000 !important ; 279 | } -------------------------------------------------------------------------------- /docs/build-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docco --css docco-fb.css ./../examples/plugin_example.js --output ./ -------------------------------------------------------------------------------- /docs/docco-fb.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Typography ----------------------------*/ 2 | 3 | @import url(http://fonts.googleapis.com/css?family=Montserrat); 4 | 5 | @font-face 6 | { 7 | font-family : 'aller-light'; 8 | src : url('public/fonts/aller-light.eot'); 9 | src : url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'), url('public/fonts/aller-light.woff') format('woff'), url('public/fonts/aller-light.ttf') format('truetype'); 10 | font-weight : normal; 11 | font-style : normal; 12 | } 13 | 14 | @font-face 15 | { 16 | font-family : 'aller-bold'; 17 | src : url('public/fonts/aller-bold.eot'); 18 | src : url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'), url('public/fonts/aller-bold.woff') format('woff'), url('public/fonts/aller-bold.ttf') format('truetype'); 19 | font-weight : normal; 20 | font-style : normal; 21 | } 22 | 23 | @font-face 24 | { 25 | font-family : 'novecento-bold'; 26 | src : url('public/fonts/novecento-bold.eot'); 27 | src : url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'), url('public/fonts/novecento-bold.woff') format('woff'), url('public/fonts/novecento-bold.ttf') format('truetype'); 28 | font-weight : normal; 29 | font-style : normal; 30 | } 31 | 32 | /*--------------------- Layout ----------------------------*/ 33 | html 34 | { 35 | height : 100%; 36 | } 37 | 38 | body 39 | { 40 | font-family : "Helvetica Neue", Helvetica, Arial, sans-serif; 41 | font-size : 14px; 42 | line-height : 18px; 43 | color : #30404f; 44 | margin : 0; 45 | padding : 0; 46 | height : 100%; 47 | } 48 | 49 | #container 50 | { 51 | min-height : 100%; 52 | } 53 | 54 | a 55 | { 56 | color : #000; 57 | } 58 | 59 | b, strong 60 | { 61 | font-weight : 400; 62 | color : #fff; 63 | } 64 | 65 | p, ul, ol 66 | { 67 | margin : 15px 0 0px; 68 | } 69 | 70 | h1, h2, h3, h4, h5, h6 71 | { 72 | font-family : montserrat, sans-serif; 73 | color : #fff; 74 | line-height : 1em; 75 | font-weight : normal; 76 | text-transform : uppercase; 77 | margin : 30px 0 15px 0; 78 | } 79 | 80 | h1 81 | { 82 | margin-top : 40px; 83 | } 84 | 85 | hr 86 | { 87 | border : 0; 88 | background : 1px solid #ddd; 89 | height : 1px; 90 | margin : 20px 0; 91 | } 92 | 93 | pre, tt, code 94 | { 95 | font-size : 12px; 96 | line-height : 16px; 97 | font-family : Menlo, Monaco, Consolas, "Lucida Console", monospace; 98 | margin : 0; 99 | padding : 0; 100 | } 101 | 102 | .annotation 103 | { 104 | color: #A5A5A5; 105 | font-weight : 200; 106 | font-size: 14px; 107 | } 108 | 109 | .annotation pre 110 | { 111 | display : block; 112 | margin : 0; 113 | padding : 7px 10px; 114 | background : #fcfcfc; 115 | -moz-box-shadow : inset 0 0 10px rgba(0, 0, 0, 0.1); 116 | -webkit-box-shadow : inset 0 0 10px rgba(0, 0, 0, 0.1); 117 | box-shadow : inset 0 0 10px rgba(0, 0, 0, 0.1); 118 | overflow-x : auto; 119 | } 120 | 121 | .annotation pre code 122 | { 123 | border : 0; 124 | padding : 0; 125 | background : transparent; 126 | } 127 | 128 | blockquote 129 | { 130 | border-left : 5px solid #ccc; 131 | margin : 0; 132 | padding : 1px 0 1px 1em; 133 | } 134 | 135 | .sections blockquote p 136 | { 137 | font-family : Menlo, Consolas, Monaco, monospace; 138 | font-size : 12px; 139 | line-height : 16px; 140 | color : #999; 141 | margin : 10px 0 0; 142 | white-space : pre-wrap; 143 | } 144 | 145 | ul.sections 146 | { 147 | list-style : none; 148 | padding : 0 0 5px 0;; 149 | margin : 0; 150 | } 151 | 152 | /* 153 | Force border-box so that % widths fit the parent 154 | container without overlap because of margin/padding. 155 | 156 | More Info : http://www.quirksmode.org/css/box.html 157 | */ 158 | ul.sections > li > div 159 | { 160 | -moz-box-sizing : border-box; /* firefox */ 161 | -ms-box-sizing : border-box; /* ie */ 162 | -webkit-box-sizing : border-box; /* webkit */ 163 | -khtml-box-sizing : border-box; /* konqueror */ 164 | box-sizing : border-box; /* css3 */ 165 | } 166 | 167 | /*---------------------- Jump Page -----------------------------*/ 168 | #jump_to, #jump_page 169 | { 170 | margin : 0; 171 | background : white; 172 | -webkit-box-shadow : 0 0 25px #777; 173 | -moz-box-shadow : 0 0 25px #777; 174 | -webkit-border-bottom-left-radius : 5px; 175 | -moz-border-radius-bottomleft : 5px; 176 | font : 16px Arial; 177 | cursor : pointer; 178 | text-align : right; 179 | list-style : none; 180 | } 181 | 182 | #jump_to a 183 | { 184 | text-decoration : none; 185 | } 186 | 187 | #jump_to a.large 188 | { 189 | display : none; 190 | } 191 | 192 | #jump_to a.small 193 | { 194 | font-size : 22px; 195 | font-weight : bold; 196 | color : #676767; 197 | } 198 | 199 | #jump_to, #jump_wrapper 200 | { 201 | position : fixed; 202 | right : 0; 203 | top : 0; 204 | padding : 10px 15px; 205 | margin : 0; 206 | } 207 | 208 | #jump_wrapper 209 | { 210 | display : none; 211 | padding : 0; 212 | } 213 | 214 | #jump_to:hover #jump_wrapper 215 | { 216 | display : block; 217 | } 218 | 219 | #jump_page 220 | { 221 | padding : 5px 0 3px; 222 | margin : 0 0 25px 25px; 223 | } 224 | 225 | #jump_page .source 226 | { 227 | display : block; 228 | padding : 15px; 229 | text-decoration : none; 230 | border-top : 1px solid #eee; 231 | } 232 | 233 | #jump_page .source:hover 234 | { 235 | background : #f5f5ff; 236 | } 237 | 238 | #jump_page .source:first-child 239 | { 240 | } 241 | 242 | /*---------------------- Low resolutions (> 320px) ---------------------*/ 243 | @media only screen and (min-width: 320px) 244 | { 245 | .pilwrap 246 | { 247 | display : none; 248 | } 249 | 250 | ul.sections > li > div 251 | { 252 | display : block; 253 | padding : 5px 10px 0 10px; 254 | } 255 | 256 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol 257 | { 258 | padding-left : 30px; 259 | } 260 | 261 | ul.sections > li > div.content 262 | { 263 | background : #f5f5ff; 264 | overflow-x : auto; 265 | -webkit-box-shadow : inset 0 0 5px #e5e5ee; 266 | box-shadow : inset 0 0 5px #e5e5ee; 267 | border : 1px solid #dedede; 268 | margin : 5px 10px 5px 10px; 269 | padding-bottom : 5px; 270 | } 271 | 272 | ul.sections > li > div.annotation pre 273 | { 274 | margin : 7px 0 7px; 275 | padding-left : 15px; 276 | } 277 | 278 | ul.sections > li > div.annotation p tt, .annotation code 279 | { 280 | background : #f8f8ff; 281 | border : 1px solid #dedede; 282 | font-size : 12px; 283 | padding : 0 0.2em; 284 | } 285 | } 286 | 287 | /*---------------------- (> 481px) ---------------------*/ 288 | @media only screen and (min-width: 481px) 289 | { 290 | #container 291 | { 292 | position : relative; 293 | } 294 | 295 | body 296 | { 297 | background-color : #f9f9f9; 298 | font-size : 15px; 299 | line-height : 21px; 300 | } 301 | 302 | pre, tt, code 303 | { 304 | line-height : 18px; 305 | } 306 | 307 | p, ul, ol 308 | { 309 | margin : 0 0 15px; 310 | } 311 | 312 | #jump_to 313 | { 314 | padding : 5px 10px; 315 | } 316 | 317 | #jump_wrapper 318 | { 319 | padding : 0; 320 | } 321 | 322 | #jump_to, #jump_page 323 | { 324 | font : 10px Arial; 325 | text-transform : uppercase; 326 | } 327 | 328 | #jump_page .source 329 | { 330 | padding : 5px 10px; 331 | } 332 | 333 | #jump_to a.large 334 | { 335 | display : inline-block; 336 | } 337 | 338 | #jump_to a.small 339 | { 340 | display : none; 341 | } 342 | 343 | #background 344 | { 345 | position : absolute; 346 | top : 0; 347 | bottom : 0; 348 | width : 350px; 349 | background : #313131; 350 | border-right : 1px solid #e5e5ee; 351 | z-index : -1; 352 | } 353 | 354 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol 355 | { 356 | padding-left : 40px; 357 | } 358 | 359 | ul.sections > li 360 | { 361 | white-space : nowrap; 362 | } 363 | 364 | ul.sections > li > div 365 | { 366 | display : inline-block; 367 | } 368 | 369 | ul.sections > li > div.annotation 370 | { 371 | max-width : 350px; 372 | min-width : 350px; 373 | min-height : 5px; 374 | padding : 13px; 375 | overflow-x : hidden; 376 | white-space : normal; 377 | vertical-align : top; 378 | text-align : left; 379 | } 380 | 381 | ul.sections > li > div.annotation pre 382 | { 383 | margin : 15px 0 15px; 384 | padding-left : 15px; 385 | } 386 | 387 | ul.sections > li > div.content 388 | { 389 | padding : 13px; 390 | vertical-align : top; 391 | background : #f9f9f9; 392 | border : none; 393 | -webkit-box-shadow : none; 394 | box-shadow : none; 395 | } 396 | 397 | .pilwrap 398 | { 399 | position : relative; 400 | display : inline; 401 | } 402 | 403 | .pilcrow 404 | { 405 | font : 12px Arial; 406 | text-decoration : none; 407 | color : #454545; 408 | position : absolute; 409 | top : 3px; 410 | left : -20px; 411 | padding : 1px 2px; 412 | opacity : 0; 413 | -webkit-transition : opacity 0.2s linear; 414 | } 415 | 416 | .for-h1 .pilcrow 417 | { 418 | top : 47px; 419 | } 420 | 421 | .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow 422 | { 423 | top : 35px; 424 | } 425 | 426 | ul.sections > li > div.annotation:hover .pilcrow 427 | { 428 | opacity : 1; 429 | } 430 | } 431 | 432 | /*---------------------- (> 1025px) ---------------------*/ 433 | @media only screen and (min-width: 1025px) 434 | { 435 | 436 | body 437 | { 438 | font-size : 16px; 439 | line-height : 24px; 440 | } 441 | 442 | #background 443 | { 444 | width : 525px; 445 | } 446 | 447 | ul.sections > li > div.annotation 448 | { 449 | max-width : 525px; 450 | min-width : 525px; 451 | padding : 10px 25px 1px 50px; 452 | } 453 | 454 | ul.sections > li > div.content 455 | { 456 | padding : 9px 15px 16px 25px; 457 | } 458 | } 459 | 460 | /*---------------------- Syntax Highlighting -----------------------------*/ 461 | 462 | td.linenos 463 | { 464 | background-color : #f0f0f0; 465 | padding-right : 10px; 466 | } 467 | 468 | span.lineno 469 | { 470 | background-color : #f0f0f0; 471 | padding : 0 5px 0 5px; 472 | } 473 | 474 | /* 475 | 476 | github.com style (c) Vasily Polovnyov 477 | 478 | */ 479 | 480 | pre code 481 | { 482 | display : block; 483 | padding : 0.5em; 484 | color : #000; 485 | background : #f8f8ff 486 | } 487 | 488 | pre .comment, 489 | pre .template_comment, 490 | pre .diff .header, 491 | pre .javadoc 492 | { 493 | color : #408080; 494 | font-style : italic 495 | } 496 | 497 | pre .keyword, 498 | pre .assignment, 499 | pre .literal, 500 | pre .css .rule .keyword, 501 | pre .winutils, 502 | pre .javascript .title, 503 | pre .lisp .title, 504 | pre .subst 505 | { 506 | color : #954121; 507 | /*font-weight: bold*/ 508 | } 509 | 510 | pre .number, 511 | pre .hexcolor 512 | { 513 | color : #40a070 514 | } 515 | 516 | pre .string, 517 | pre .tag .value, 518 | pre .phpdoc, 519 | pre .tex .formula 520 | { 521 | color : #219161; 522 | } 523 | 524 | pre .title, 525 | pre .id 526 | { 527 | color : #19469D; 528 | } 529 | 530 | pre .params 531 | { 532 | color : #00F; 533 | } 534 | 535 | pre .javascript .title, 536 | pre .lisp .title, 537 | pre .subst 538 | { 539 | font-weight : normal 540 | } 541 | 542 | pre .class .title, 543 | pre .haskell .label, 544 | pre .tex .command 545 | { 546 | color : #458; 547 | font-weight : bold 548 | } 549 | 550 | pre .tag, 551 | pre .tag .title, 552 | pre .rules .property, 553 | pre .django .tag .keyword 554 | { 555 | color : #000080; 556 | font-weight : normal 557 | } 558 | 559 | pre .attribute, 560 | pre .variable, 561 | pre .instancevar, 562 | pre .lisp .body 563 | { 564 | color : #008080 565 | } 566 | 567 | pre .regexp 568 | { 569 | color : #B68 570 | } 571 | 572 | pre .class 573 | { 574 | color : #458; 575 | font-weight : bold 576 | } 577 | 578 | pre .symbol, 579 | pre .ruby .symbol .string, 580 | pre .ruby .symbol .keyword, 581 | pre .ruby .symbol .keymethods, 582 | pre .lisp .keyword, 583 | pre .tex .special, 584 | pre .input_number 585 | { 586 | color : #990073 587 | } 588 | 589 | pre .builtin, 590 | pre .constructor, 591 | pre .built_in, 592 | pre .lisp .title 593 | { 594 | color : #0086b3 595 | } 596 | 597 | pre .preprocessor, 598 | pre .pi, 599 | pre .doctype, 600 | pre .shebang, 601 | pre .cdata 602 | { 603 | color : #999; 604 | font-weight : bold 605 | } 606 | 607 | pre .deletion 608 | { 609 | background : #fdd 610 | } 611 | 612 | pre .addition 613 | { 614 | background : #dfd 615 | } 616 | 617 | pre .diff .change 618 | { 619 | background : #0086b3 620 | } 621 | 622 | pre .chunk 623 | { 624 | color : #aaa 625 | } 626 | 627 | pre .tex .formula 628 | { 629 | opacity : 0.5; 630 | } 631 | -------------------------------------------------------------------------------- /docs/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Typography ----------------------------*/ 2 | 3 | @font-face { 4 | font-family: 'aller-light'; 5 | src: url('public/fonts/aller-light.eot'); 6 | src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'), 7 | url('public/fonts/aller-light.woff') format('woff'), 8 | url('public/fonts/aller-light.ttf') format('truetype'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'aller-bold'; 15 | src: url('public/fonts/aller-bold.eot'); 16 | src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'), 17 | url('public/fonts/aller-bold.woff') format('woff'), 18 | url('public/fonts/aller-bold.ttf') format('truetype'); 19 | font-weight: normal; 20 | font-style: normal; 21 | } 22 | 23 | @font-face { 24 | font-family: 'novecento-bold'; 25 | src: url('public/fonts/novecento-bold.eot'); 26 | src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'), 27 | url('public/fonts/novecento-bold.woff') format('woff'), 28 | url('public/fonts/novecento-bold.ttf') format('truetype'); 29 | font-weight: normal; 30 | font-style: normal; 31 | } 32 | 33 | /*--------------------- Layout ----------------------------*/ 34 | html { height: 100%; } 35 | body { 36 | font-family: "aller-light"; 37 | font-size: 14px; 38 | line-height: 18px; 39 | color: #30404f; 40 | margin: 0; padding: 0; 41 | height:100%; 42 | } 43 | #container { min-height: 100%; } 44 | 45 | a { 46 | color: #000; 47 | } 48 | 49 | b, strong { 50 | font-weight: normal; 51 | font-family: "aller-bold"; 52 | } 53 | 54 | p, ul, ol { 55 | margin: 15px 0 0px; 56 | } 57 | 58 | h1, h2, h3, h4, h5, h6 { 59 | color: #112233; 60 | line-height: 1em; 61 | font-weight: normal; 62 | font-family: "novecento-bold"; 63 | text-transform: uppercase; 64 | margin: 30px 0 15px 0; 65 | } 66 | 67 | h1 { 68 | margin-top: 40px; 69 | } 70 | 71 | hr { 72 | border: 0; 73 | background: 1px solid #ddd; 74 | height: 1px; 75 | margin: 20px 0; 76 | } 77 | 78 | pre, tt, code { 79 | font-size: 12px; line-height: 16px; 80 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 81 | margin: 0; padding: 0; 82 | } 83 | .annotation pre { 84 | display: block; 85 | margin: 0; 86 | padding: 7px 10px; 87 | background: #fcfcfc; 88 | -moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 89 | -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 90 | box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 91 | overflow-x: auto; 92 | } 93 | .annotation pre code { 94 | border: 0; 95 | padding: 0; 96 | background: transparent; 97 | } 98 | 99 | 100 | blockquote { 101 | border-left: 5px solid #ccc; 102 | margin: 0; 103 | padding: 1px 0 1px 1em; 104 | } 105 | .sections blockquote p { 106 | font-family: Menlo, Consolas, Monaco, monospace; 107 | font-size: 12px; line-height: 16px; 108 | color: #999; 109 | margin: 10px 0 0; 110 | white-space: pre-wrap; 111 | } 112 | 113 | ul.sections { 114 | list-style: none; 115 | padding:0 0 5px 0;; 116 | margin:0; 117 | } 118 | 119 | /* 120 | Force border-box so that % widths fit the parent 121 | container without overlap because of margin/padding. 122 | 123 | More Info : http://www.quirksmode.org/css/box.html 124 | */ 125 | ul.sections > li > div { 126 | -moz-box-sizing: border-box; /* firefox */ 127 | -ms-box-sizing: border-box; /* ie */ 128 | -webkit-box-sizing: border-box; /* webkit */ 129 | -khtml-box-sizing: border-box; /* konqueror */ 130 | box-sizing: border-box; /* css3 */ 131 | } 132 | 133 | 134 | /*---------------------- Jump Page -----------------------------*/ 135 | #jump_to, #jump_page { 136 | margin: 0; 137 | background: white; 138 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 139 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 140 | font: 16px Arial; 141 | cursor: pointer; 142 | text-align: right; 143 | list-style: none; 144 | } 145 | 146 | #jump_to a { 147 | text-decoration: none; 148 | } 149 | 150 | #jump_to a.large { 151 | display: none; 152 | } 153 | #jump_to a.small { 154 | font-size: 22px; 155 | font-weight: bold; 156 | color: #676767; 157 | } 158 | 159 | #jump_to, #jump_wrapper { 160 | position: fixed; 161 | right: 0; top: 0; 162 | padding: 10px 15px; 163 | margin:0; 164 | } 165 | 166 | #jump_wrapper { 167 | display: none; 168 | padding:0; 169 | } 170 | 171 | #jump_to:hover #jump_wrapper { 172 | display: block; 173 | } 174 | 175 | #jump_page { 176 | padding: 5px 0 3px; 177 | margin: 0 0 25px 25px; 178 | } 179 | 180 | #jump_page .source { 181 | display: block; 182 | padding: 15px; 183 | text-decoration: none; 184 | border-top: 1px solid #eee; 185 | } 186 | 187 | #jump_page .source:hover { 188 | background: #f5f5ff; 189 | } 190 | 191 | #jump_page .source:first-child { 192 | } 193 | 194 | /*---------------------- Low resolutions (> 320px) ---------------------*/ 195 | @media only screen and (min-width: 320px) { 196 | .pilwrap { display: none; } 197 | 198 | ul.sections > li > div { 199 | display: block; 200 | padding:5px 10px 0 10px; 201 | } 202 | 203 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 204 | padding-left: 30px; 205 | } 206 | 207 | ul.sections > li > div.content { 208 | background: #f5f5ff; 209 | overflow-x:auto; 210 | -webkit-box-shadow: inset 0 0 5px #e5e5ee; 211 | box-shadow: inset 0 0 5px #e5e5ee; 212 | border: 1px solid #dedede; 213 | margin:5px 10px 5px 10px; 214 | padding-bottom: 5px; 215 | } 216 | 217 | ul.sections > li > div.annotation pre { 218 | margin: 7px 0 7px; 219 | padding-left: 15px; 220 | } 221 | 222 | ul.sections > li > div.annotation p tt, .annotation code { 223 | background: #f8f8ff; 224 | border: 1px solid #dedede; 225 | font-size: 12px; 226 | padding: 0 0.2em; 227 | } 228 | } 229 | 230 | /*---------------------- (> 481px) ---------------------*/ 231 | @media only screen and (min-width: 481px) { 232 | #container { 233 | position: relative; 234 | } 235 | body { 236 | background-color: #F5F5FF; 237 | font-size: 15px; 238 | line-height: 21px; 239 | } 240 | pre, tt, code { 241 | line-height: 18px; 242 | } 243 | p, ul, ol { 244 | margin: 0 0 15px; 245 | } 246 | 247 | 248 | #jump_to { 249 | padding: 5px 10px; 250 | } 251 | #jump_wrapper { 252 | padding: 0; 253 | } 254 | #jump_to, #jump_page { 255 | font: 10px Arial; 256 | text-transform: uppercase; 257 | } 258 | #jump_page .source { 259 | padding: 5px 10px; 260 | } 261 | #jump_to a.large { 262 | display: inline-block; 263 | } 264 | #jump_to a.small { 265 | display: none; 266 | } 267 | 268 | 269 | 270 | #background { 271 | position: absolute; 272 | top: 0; bottom: 0; 273 | width: 350px; 274 | background: #fff; 275 | border-right: 1px solid #e5e5ee; 276 | z-index: -1; 277 | } 278 | 279 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 280 | padding-left: 40px; 281 | } 282 | 283 | ul.sections > li { 284 | white-space: nowrap; 285 | } 286 | 287 | ul.sections > li > div { 288 | display: inline-block; 289 | } 290 | 291 | ul.sections > li > div.annotation { 292 | max-width: 350px; 293 | min-width: 350px; 294 | min-height: 5px; 295 | padding: 13px; 296 | overflow-x: hidden; 297 | white-space: normal; 298 | vertical-align: top; 299 | text-align: left; 300 | } 301 | ul.sections > li > div.annotation pre { 302 | margin: 15px 0 15px; 303 | padding-left: 15px; 304 | } 305 | 306 | ul.sections > li > div.content { 307 | padding: 13px; 308 | vertical-align: top; 309 | background: #f5f5ff; 310 | border: none; 311 | -webkit-box-shadow: none; 312 | box-shadow: none; 313 | } 314 | 315 | .pilwrap { 316 | position: relative; 317 | display: inline; 318 | } 319 | 320 | .pilcrow { 321 | font: 12px Arial; 322 | text-decoration: none; 323 | color: #454545; 324 | position: absolute; 325 | top: 3px; left: -20px; 326 | padding: 1px 2px; 327 | opacity: 0; 328 | -webkit-transition: opacity 0.2s linear; 329 | } 330 | .for-h1 .pilcrow { 331 | top: 47px; 332 | } 333 | .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow { 334 | top: 35px; 335 | } 336 | 337 | ul.sections > li > div.annotation:hover .pilcrow { 338 | opacity: 1; 339 | } 340 | } 341 | 342 | /*---------------------- (> 1025px) ---------------------*/ 343 | @media only screen and (min-width: 1025px) { 344 | 345 | body { 346 | font-size: 16px; 347 | line-height: 24px; 348 | } 349 | 350 | #background { 351 | width: 525px; 352 | } 353 | ul.sections > li > div.annotation { 354 | max-width: 525px; 355 | min-width: 525px; 356 | padding: 10px 25px 1px 50px; 357 | } 358 | ul.sections > li > div.content { 359 | padding: 9px 15px 16px 25px; 360 | } 361 | } 362 | 363 | /*---------------------- Syntax Highlighting -----------------------------*/ 364 | 365 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 366 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 367 | /* 368 | 369 | github.com style (c) Vasily Polovnyov 370 | 371 | */ 372 | 373 | pre code { 374 | display: block; padding: 0.5em; 375 | color: #000; 376 | background: #f8f8ff 377 | } 378 | 379 | pre .comment, 380 | pre .template_comment, 381 | pre .diff .header, 382 | pre .javadoc { 383 | color: #408080; 384 | font-style: italic 385 | } 386 | 387 | pre .keyword, 388 | pre .assignment, 389 | pre .literal, 390 | pre .css .rule .keyword, 391 | pre .winutils, 392 | pre .javascript .title, 393 | pre .lisp .title, 394 | pre .subst { 395 | color: #954121; 396 | /*font-weight: bold*/ 397 | } 398 | 399 | pre .number, 400 | pre .hexcolor { 401 | color: #40a070 402 | } 403 | 404 | pre .string, 405 | pre .tag .value, 406 | pre .phpdoc, 407 | pre .tex .formula { 408 | color: #219161; 409 | } 410 | 411 | pre .title, 412 | pre .id { 413 | color: #19469D; 414 | } 415 | pre .params { 416 | color: #00F; 417 | } 418 | 419 | pre .javascript .title, 420 | pre .lisp .title, 421 | pre .subst { 422 | font-weight: normal 423 | } 424 | 425 | pre .class .title, 426 | pre .haskell .label, 427 | pre .tex .command { 428 | color: #458; 429 | font-weight: bold 430 | } 431 | 432 | pre .tag, 433 | pre .tag .title, 434 | pre .rules .property, 435 | pre .django .tag .keyword { 436 | color: #000080; 437 | font-weight: normal 438 | } 439 | 440 | pre .attribute, 441 | pre .variable, 442 | pre .instancevar, 443 | pre .lisp .body { 444 | color: #008080 445 | } 446 | 447 | pre .regexp { 448 | color: #B68 449 | } 450 | 451 | pre .class { 452 | color: #458; 453 | font-weight: bold 454 | } 455 | 456 | pre .symbol, 457 | pre .ruby .symbol .string, 458 | pre .ruby .symbol .keyword, 459 | pre .ruby .symbol .keymethods, 460 | pre .lisp .keyword, 461 | pre .tex .special, 462 | pre .input_number { 463 | color: #990073 464 | } 465 | 466 | pre .builtin, 467 | pre .constructor, 468 | pre .built_in, 469 | pre .lisp .title { 470 | color: #0086b3 471 | } 472 | 473 | pre .preprocessor, 474 | pre .pi, 475 | pre .doctype, 476 | pre .shebang, 477 | pre .cdata { 478 | color: #999; 479 | font-weight: bold 480 | } 481 | 482 | pre .deletion { 483 | background: #fdd 484 | } 485 | 486 | pre .addition { 487 | background: #dfd 488 | } 489 | 490 | pre .diff .change { 491 | background: #0086b3 492 | } 493 | 494 | pre .chunk { 495 | color: #aaa 496 | } 497 | 498 | pre .tex .formula { 499 | opacity: 0.5; 500 | } 501 | -------------------------------------------------------------------------------- /docs/public/fonts/aller-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/docs/public/fonts/aller-bold.eot -------------------------------------------------------------------------------- /docs/public/fonts/aller-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/docs/public/fonts/aller-bold.ttf -------------------------------------------------------------------------------- /docs/public/fonts/aller-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/docs/public/fonts/aller-bold.woff -------------------------------------------------------------------------------- /docs/public/fonts/aller-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/docs/public/fonts/aller-light.eot -------------------------------------------------------------------------------- /docs/public/fonts/aller-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/docs/public/fonts/aller-light.ttf -------------------------------------------------------------------------------- /docs/public/fonts/aller-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/docs/public/fonts/aller-light.woff -------------------------------------------------------------------------------- /docs/public/fonts/fleurons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/docs/public/fonts/fleurons.eot -------------------------------------------------------------------------------- /docs/public/fonts/fleurons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/docs/public/fonts/fleurons.ttf -------------------------------------------------------------------------------- /docs/public/fonts/fleurons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/docs/public/fonts/fleurons.woff -------------------------------------------------------------------------------- /docs/public/fonts/novecento-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/docs/public/fonts/novecento-bold.eot -------------------------------------------------------------------------------- /docs/public/fonts/novecento-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/docs/public/fonts/novecento-bold.ttf -------------------------------------------------------------------------------- /docs/public/fonts/novecento-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/docs/public/fonts/novecento-bold.woff -------------------------------------------------------------------------------- /docs/public/images/gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/docs/public/images/gray.png -------------------------------------------------------------------------------- /docs/public/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 8/9. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | } 34 | 35 | /* 36 | * Prevents modern browsers from displaying `audio` without controls. 37 | * Remove excess height in iOS 5 devices. 38 | */ 39 | 40 | audio:not([controls]) { 41 | display: none; 42 | height: 0; 43 | } 44 | 45 | /* 46 | * Addresses styling for `hidden` attribute not present in IE 8/9. 47 | */ 48 | 49 | [hidden] { 50 | display: none; 51 | } 52 | 53 | /* ========================================================================== 54 | Base 55 | ========================================================================== */ 56 | 57 | /* 58 | * 1. Sets default font family to sans-serif. 59 | * 2. Prevents iOS text size adjust after orientation change, without disabling 60 | * user zoom. 61 | */ 62 | 63 | html { 64 | font-family: sans-serif; /* 1 */ 65 | -webkit-text-size-adjust: 100%; /* 2 */ 66 | -ms-text-size-adjust: 100%; /* 2 */ 67 | } 68 | 69 | /* 70 | * Removes default margin. 71 | */ 72 | 73 | body { 74 | margin: 0; 75 | } 76 | 77 | /* ========================================================================== 78 | Links 79 | ========================================================================== */ 80 | 81 | /* 82 | * Addresses `outline` inconsistency between Chrome and other browsers. 83 | */ 84 | 85 | a:focus { 86 | outline: thin dotted; 87 | } 88 | 89 | /* 90 | * Improves readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* ========================================================================== 99 | Typography 100 | ========================================================================== */ 101 | 102 | /* 103 | * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, 104 | * Safari 5, and Chrome. 105 | */ 106 | 107 | h1 { 108 | font-size: 2em; 109 | } 110 | 111 | /* 112 | * Addresses styling not present in IE 8/9, Safari 5, and Chrome. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: 1px dotted; 117 | } 118 | 119 | /* 120 | * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: bold; 126 | } 127 | 128 | /* 129 | * Addresses styling not present in Safari 5 and Chrome. 130 | */ 131 | 132 | dfn { 133 | font-style: italic; 134 | } 135 | 136 | /* 137 | * Addresses styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | 146 | /* 147 | * Corrects font family set oddly in Safari 5 and Chrome. 148 | */ 149 | 150 | code, 151 | kbd, 152 | pre, 153 | samp { 154 | font-family: monospace, serif; 155 | font-size: 1em; 156 | } 157 | 158 | /* 159 | * Improves readability of pre-formatted text in all browsers. 160 | */ 161 | 162 | pre { 163 | white-space: pre; 164 | white-space: pre-wrap; 165 | word-wrap: break-word; 166 | } 167 | 168 | /* 169 | * Sets consistent quote types. 170 | */ 171 | 172 | q { 173 | quotes: "\201C" "\201D" "\2018" "\2019"; 174 | } 175 | 176 | /* 177 | * Addresses inconsistent and variable font size in all browsers. 178 | */ 179 | 180 | small { 181 | font-size: 80%; 182 | } 183 | 184 | /* 185 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 186 | */ 187 | 188 | sub, 189 | sup { 190 | font-size: 75%; 191 | line-height: 0; 192 | position: relative; 193 | vertical-align: baseline; 194 | } 195 | 196 | sup { 197 | top: -0.5em; 198 | } 199 | 200 | sub { 201 | bottom: -0.25em; 202 | } 203 | 204 | /* ========================================================================== 205 | Embedded content 206 | ========================================================================== */ 207 | 208 | /* 209 | * Removes border when inside `a` element in IE 8/9. 210 | */ 211 | 212 | img { 213 | border: 0; 214 | } 215 | 216 | /* 217 | * Corrects overflow displayed oddly in IE 9. 218 | */ 219 | 220 | svg:not(:root) { 221 | overflow: hidden; 222 | } 223 | 224 | /* ========================================================================== 225 | Figures 226 | ========================================================================== */ 227 | 228 | /* 229 | * Addresses margin not present in IE 8/9 and Safari 5. 230 | */ 231 | 232 | figure { 233 | margin: 0; 234 | } 235 | 236 | /* ========================================================================== 237 | Forms 238 | ========================================================================== */ 239 | 240 | /* 241 | * Define consistent border, margin, and padding. 242 | */ 243 | 244 | fieldset { 245 | border: 1px solid #c0c0c0; 246 | margin: 0 2px; 247 | padding: 0.35em 0.625em 0.75em; 248 | } 249 | 250 | /* 251 | * 1. Corrects color not being inherited in IE 8/9. 252 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 253 | */ 254 | 255 | legend { 256 | border: 0; /* 1 */ 257 | padding: 0; /* 2 */ 258 | } 259 | 260 | /* 261 | * 1. Corrects font family not being inherited in all browsers. 262 | * 2. Corrects font size not being inherited in all browsers. 263 | * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome 264 | */ 265 | 266 | button, 267 | input, 268 | select, 269 | textarea { 270 | font-family: inherit; /* 1 */ 271 | font-size: 100%; /* 2 */ 272 | margin: 0; /* 3 */ 273 | } 274 | 275 | /* 276 | * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in 277 | * the UA stylesheet. 278 | */ 279 | 280 | button, 281 | input { 282 | line-height: normal; 283 | } 284 | 285 | /* 286 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 287 | * and `video` controls. 288 | * 2. Corrects inability to style clickable `input` types in iOS. 289 | * 3. Improves usability and consistency of cursor style between image-type 290 | * `input` and others. 291 | */ 292 | 293 | button, 294 | html input[type="button"], /* 1 */ 295 | input[type="reset"], 296 | input[type="submit"] { 297 | -webkit-appearance: button; /* 2 */ 298 | cursor: pointer; /* 3 */ 299 | } 300 | 301 | /* 302 | * Re-set default cursor for disabled elements. 303 | */ 304 | 305 | button[disabled], 306 | input[disabled] { 307 | cursor: default; 308 | } 309 | 310 | /* 311 | * 1. Addresses box sizing set to `content-box` in IE 8/9. 312 | * 2. Removes excess padding in IE 8/9. 313 | */ 314 | 315 | input[type="checkbox"], 316 | input[type="radio"] { 317 | box-sizing: border-box; /* 1 */ 318 | padding: 0; /* 2 */ 319 | } 320 | 321 | /* 322 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 323 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 324 | * (include `-moz` to future-proof). 325 | */ 326 | 327 | input[type="search"] { 328 | -webkit-appearance: textfield; /* 1 */ 329 | -moz-box-sizing: content-box; 330 | -webkit-box-sizing: content-box; /* 2 */ 331 | box-sizing: content-box; 332 | } 333 | 334 | /* 335 | * Removes inner padding and search cancel button in Safari 5 and Chrome 336 | * on OS X. 337 | */ 338 | 339 | input[type="search"]::-webkit-search-cancel-button, 340 | input[type="search"]::-webkit-search-decoration { 341 | -webkit-appearance: none; 342 | } 343 | 344 | /* 345 | * Removes inner padding and border in Firefox 4+. 346 | */ 347 | 348 | button::-moz-focus-inner, 349 | input::-moz-focus-inner { 350 | border: 0; 351 | padding: 0; 352 | } 353 | 354 | /* 355 | * 1. Removes default vertical scrollbar in IE 8/9. 356 | * 2. Improves readability and alignment in all browsers. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; /* 1 */ 361 | vertical-align: top; /* 2 */ 362 | } 363 | 364 | /* ========================================================================== 365 | Tables 366 | ========================================================================== */ 367 | 368 | /* 369 | * Remove most spacing between table cells. 370 | */ 371 | 372 | table { 373 | border-collapse: collapse; 374 | border-spacing: 0; 375 | } -------------------------------------------------------------------------------- /examples/altGuage.js: -------------------------------------------------------------------------------- 1 | window.dyngaugeID = 0; 2 | (function() { 3 | var dynamicGaugeWidget = function (settings) { 4 | var self = this; 5 | thisDynGaugeID = "dyngauge-" + window.dyngaugeID++; 6 | var titleElement = $('

'); 7 | var gaugeElement = $('
'); 8 | 9 | var gaugeObject; 10 | var rendered = false; 11 | 12 | var currentSettings = settings; 13 | 14 | function createGauge() { 15 | if (!rendered) { 16 | return; 17 | } 18 | 19 | gaugeElement.empty(); 20 | 21 | gaugeObject = new JustGage({ 22 | id: thisDynGaugeID, 23 | value: (_.isUndefined(currentSettings.min_value) ? 0 : currentSettings.min_value), 24 | min: (_.isUndefined(currentSettings.min_value) ? 0 : currentSettings.min_value), 25 | max: (_.isUndefined(currentSettings.max_value) ? 0 : currentSettings.max_value), 26 | label: currentSettings.units, 27 | showInnerShadow: false, 28 | valueFontColor: "#d3d4d4", 29 | levelColors: ['#ff0000', '#ffa500','#ffa500','#ffff00', '#00ff00'] 30 | }); 31 | } 32 | 33 | this.render = function (element) { 34 | rendered = true; 35 | $(element).append(titleElement).append($('
').append(gaugeElement)); 36 | createGauge(); 37 | } 38 | 39 | this.onSettingsChanged = function (newSettings) { 40 | if (newSettings.min_value != currentSettings.min_value || newSettings.max_value != currentSettings.max_value || newSettings.units != currentSettings.units) { 41 | currentSettings = newSettings; 42 | createGauge(); 43 | } 44 | else { 45 | currentSettings = newSettings; 46 | } 47 | 48 | titleElement.html(newSettings.title); 49 | } 50 | 51 | this.onCalculatedValueChanged = function (settingName, newValue) { 52 | if (!_.isUndefined(gaugeObject)) { 53 | gaugeObject.refresh(Number(newValue)); 54 | } 55 | } 56 | 57 | this.onDispose = function () { 58 | } 59 | 60 | this.getHeight = function () { 61 | return 3; 62 | } 63 | 64 | this.onSettingsChanged(settings); 65 | }; 66 | 67 | freeboard.loadWidgetPlugin({ 68 | type_name: "dyngauge", 69 | display_name: "DynamicGauge", 70 | "external_scripts" : [ 71 | "plugins/thirdparty/raphael.2.1.0.min.js", 72 | "plugins/thirdparty/justgage.1.0.1.js" 73 | ], 74 | settings: [ 75 | { 76 | name: "title", 77 | display_name: "Title", 78 | type: "text" 79 | }, 80 | { 81 | name: "value", 82 | display_name: "Value", 83 | type: "calculated" 84 | }, 85 | { 86 | name: "units", 87 | display_name: "Units", 88 | type: "text" 89 | }, 90 | { 91 | name: "min_value", 92 | display_name: "Minimum", 93 | type: "text", 94 | default_value: 0 95 | }, 96 | { 97 | name: "max_value", 98 | display_name: "Maximum", 99 | type: "text", 100 | default_value: 100 101 | } 102 | ], 103 | newInstance: function (settings, newInstanceCallback) { 104 | newInstanceCallback(new dynamicGaugeWidget(settings)); 105 | } 106 | }); 107 | 108 | }()); 109 | 110 | -------------------------------------------------------------------------------- /examples/rl78.json: -------------------------------------------------------------------------------- 1 | { 2 | "allow_edit" : true, 3 | "header_image" : "https://raw.github.com/Freeboard/branding/master/renesas/renesas_logo.png", 4 | "panes": [ 5 | { 6 | "title" : "Tilt", 7 | "width" : 1, 8 | "row" : { "3": 1 }, 9 | "col" : { "3": 1 }, 10 | "widgets": [ 11 | { 12 | "type" : "pointer", 13 | "settings": { 14 | "direction" : "datasources.RL78.Acceleration_X * -90", 15 | "value_text": "(datasources.RL78.Acceleration_X * -90).toFixed(0)", 16 | "units" : "degrees" 17 | } 18 | }, 19 | { 20 | "type" : "text_widget", 21 | "settings": { 22 | "title" : "Y", 23 | "size" : "regular", 24 | "value" : "(datasources.RL78.Acceleration_Y * 90).toFixed(0)", 25 | "sparkline": true, 26 | "animate" : true, 27 | "units" : "°" 28 | } 29 | }, 30 | { 31 | "type" : "text_widget", 32 | "settings": { 33 | "title" : "Z", 34 | "size" : "regular", 35 | "value" : "(datasources.RL78.Acceleration_Z * -90).toFixed(0)", 36 | "sparkline": true, 37 | "animate" : true, 38 | "units" : "°" 39 | } 40 | } 41 | ] 42 | }, 43 | { 44 | "title" : "Buttons", 45 | "width" : 1, 46 | "row" : { "3": 5 }, 47 | "col" : { "3": 3 }, 48 | "widgets": [ 49 | { 50 | "type" : "indicator", 51 | "settings": { 52 | "value" : "datasources.RL78.Button_1", 53 | "on_text" : "Button 1 is ON", 54 | "off_text": "Button 1 is OFF" 55 | } 56 | }, 57 | { 58 | "type" : "indicator", 59 | "settings": { 60 | "value" : "datasources.RL78.Button_2", 61 | "on_text" : "Button 2 is ON", 62 | "off_text": "Button 2 is OFF" 63 | } 64 | }, 65 | { 66 | "type" : "indicator", 67 | "settings": { 68 | "value" : "datasources.RL78.Button_3", 69 | "on_text" : "Button 3 is ON", 70 | "off_text": "Button 3 is OFF" 71 | } 72 | } 73 | ] 74 | }, 75 | { 76 | "title" : "Temperature", 77 | "width" : 1, 78 | "row" : { "3": 1 }, 79 | "col" : { "3": 2 }, 80 | "widgets": [ 81 | { 82 | "type" : "text_widget", 83 | "settings": { 84 | "title" : "Indoor", 85 | "size" : "big", 86 | "value" : "datasources.RL78.Temperature", 87 | "sparkline": false, 88 | "animate" : true, 89 | "units" : "°F" 90 | } 91 | }, 92 | { 93 | "type" : "text_widget", 94 | "settings": { 95 | "title" : "Outdoor", 96 | "size" : "big", 97 | "value" : "((datasources.Weather.main.temp - 273.15) * 1.8 + 32).toFixed(2)", 98 | "animate": true, 99 | "units" : "°F" 100 | } 101 | } 102 | ] 103 | }, 104 | { 105 | "title" : "Potentiometer", 106 | "width" : 1, 107 | "row" : { "3": 1 }, 108 | "col" : { "3": 3 }, 109 | "widgets": [ 110 | { 111 | "type" : "gauge", 112 | "settings": { 113 | "title" : "", 114 | "value" : "datasources.RL78.Potentiometer", 115 | "units" : "Ω", 116 | "min_value": 0, 117 | "max_value": "1000" 118 | } 119 | } 120 | ] 121 | }, 122 | { 123 | "title" : "Miscellaneous", 124 | "width" : 1, 125 | "row" : { "3": 6 }, 126 | "col" : { "3": 2 }, 127 | "widgets": [ 128 | { 129 | "type" : "text_widget", 130 | "settings": { 131 | "title" : "Light", 132 | "size" : "regular", 133 | "value" : "datasources.RL78.Light", 134 | "sparkline": true, 135 | "animate" : true 136 | } 137 | }, 138 | { 139 | "type" : "text_widget", 140 | "settings": { 141 | "title" : "Sound", 142 | "size" : "regular", 143 | "value" : "datasources.RL78.Sound", 144 | "sparkline": true, 145 | "animate" : true 146 | } 147 | } 148 | ] 149 | } 150 | ], 151 | "datasources": [ 152 | { 153 | "name" : "RL78", 154 | "type" : "rl78", 155 | "settings": { 156 | "device_resource_id": "14f762c59815e24973165668aff677659b973d62" 157 | } 158 | }, 159 | { 160 | "name" : "Weather", 161 | "type" : "JSON", 162 | "settings": { 163 | "url" : "http://api.openweathermap.org/data/2.5/weather?q=Seattle,WA", 164 | "refresh" : 5, 165 | "is_jsonp": true 166 | } 167 | } 168 | ]} -------------------------------------------------------------------------------- /examples/weather.json: -------------------------------------------------------------------------------- 1 | { 2 | "header_image" : "", 3 | "allow_edit" : true, 4 | "panes": [ 5 | { 6 | "title" : "Wind", 7 | "width" : 1, 8 | "row" : { "3" :1 }, 9 | "col" : { "3": 2 }, 10 | "widgets": [ 11 | { 12 | "type" : "pointer", 13 | "settings": { 14 | "direction" : "datasources.Weather.wind_direction", 15 | "value_text": "var dir = datasources.Weather.wind_direction;\n\nif(dir <= 22.5)\nreturn \"N\";\nelse if(dir <= 67.5)\nreturn \"NE\";\nelse if(dir <= 112.5)\nreturn \"E\";\nelse if(dir <= 157.5)\nreturn \"SE\";\nelse if(dir <= 202.5)\nreturn \"S\";\nelse if(dir <= 247.5)\nreturn \"SW\";\nelse if(dir <= 292.5)\nreturn \"W\";\nelse if(dir <= 337.5)\nreturn \"NW\";\nelse if(dir <= 360)\nreturn \"N\";" 16 | } 17 | }, 18 | { 19 | "type" : "text_widget", 20 | "settings": { 21 | "size" : "regular", 22 | "value" : "datasources.Weather.wind_speed", 23 | "sparkline": true, 24 | "animate" : true, 25 | "units" : "MPH" 26 | } 27 | } 28 | ] 29 | }, 30 | { 31 | "width" : 1, 32 | "row" : { "3": 5}, 33 | "col" : { "3": 3}, 34 | "widgets": [ 35 | { 36 | "type" : "text_widget", 37 | "settings": { 38 | "title" : "Sunrise", 39 | "size" : "regular", 40 | "value" : "datasources.Weather.sunrise", 41 | "animate": true 42 | } 43 | }, 44 | { 45 | "type" : "text_widget", 46 | "settings": { 47 | "title" : "Sunset", 48 | "size" : "regular", 49 | "value" : "datasources.Weather.sunset", 50 | "animate": true 51 | } 52 | } 53 | ] 54 | }, 55 | { 56 | "title" : "Temperature", 57 | "width" : 1, 58 | "row" : { "3": 4 }, 59 | "col" : { "3": 1 }, 60 | "widgets": [ 61 | { 62 | "type" : "text_widget", 63 | "settings": { 64 | "title" : "Current", 65 | "size" : "big", 66 | "value" : "datasources.Weather.current_temp", 67 | "animate": true, 68 | "units" : "°F" 69 | } 70 | }, 71 | { 72 | "type" : "text_widget", 73 | "settings": { 74 | "title" : "High", 75 | "size" : "regular", 76 | "value" : "datasources.Weather.high_temp", 77 | "animate": true, 78 | "units" : "°F" 79 | } 80 | }, 81 | { 82 | "type" : "text_widget", 83 | "settings": { 84 | "title" : "Low", 85 | "size" : "regular", 86 | "value" : "datasources.Weather.low_temp", 87 | "animate": true, 88 | "units" : "°F" 89 | } 90 | } 91 | ] 92 | }, 93 | { 94 | "title" : "Info", 95 | "width" : 1, 96 | "row" : { "3": 1 }, 97 | "col" : { "3": 1 }, 98 | "widgets": [ 99 | { 100 | "type" : "text_widget", 101 | "settings": { 102 | "title" : "City", 103 | "size" : "regular", 104 | "value" : "datasources.Weather.place_name", 105 | "animate": true 106 | } 107 | }, 108 | { 109 | "type" : "text_widget", 110 | "settings": { 111 | "title" : "Conditions", 112 | "size" : "regular", 113 | "value" : "datasources.Weather.conditions", 114 | "animate": true 115 | } 116 | } 117 | ] 118 | }, 119 | { 120 | "title" : "Humidity", 121 | "width" : 1, 122 | "row" : { "3": 1 }, 123 | "col" : { "3": 3 }, 124 | "widgets": [ 125 | { 126 | "type" : "gauge", 127 | "settings": { 128 | "value" : "datasources.Weather.humidity", 129 | "units" : "%", 130 | "min_value": 0, 131 | "max_value": 100 132 | } 133 | } 134 | ] 135 | }, 136 | { 137 | "title" : "Pressure", 138 | "width" : 1, 139 | "row" : { "3": 7 }, 140 | "col" : { "3": 2 }, 141 | "widgets": [ 142 | { 143 | "type" : "text_widget", 144 | "settings": { 145 | "size" : "regular", 146 | "value" : "datasources.Weather.pressure", 147 | "sparkline": true, 148 | "animate" : true, 149 | "units" : "mb" 150 | } 151 | } 152 | ] 153 | } 154 | ], "datasources": [ 155 | { 156 | "name" : "Weather", 157 | "type" : "openweathermap", 158 | "settings": { 159 | "location": "New York, NY", 160 | "units" : "imperial", 161 | "refresh" : 5 162 | } 163 | } 164 | ]} -------------------------------------------------------------------------------- /img/dropdown-arrow-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/img/dropdown-arrow-black.png -------------------------------------------------------------------------------- /img/dropdown-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/img/dropdown-arrow.png -------------------------------------------------------------------------------- /img/glyphicons-blackboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/img/glyphicons-blackboard.png -------------------------------------------------------------------------------- /img/glyphicons-halflings-grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/img/glyphicons-halflings-grey.png -------------------------------------------------------------------------------- /img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /img/glyphicons-log-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/img/glyphicons-log-in.png -------------------------------------------------------------------------------- /img/glyphicons-log-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/img/glyphicons-log-out.png -------------------------------------------------------------------------------- /img/moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/img/moon.png -------------------------------------------------------------------------------- /img/netpie-logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/img/netpie-logo-black.png -------------------------------------------------------------------------------- /img/netpie-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/img/netpie-logo-white.png -------------------------------------------------------------------------------- /img/sun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/img/sun.png -------------------------------------------------------------------------------- /index-dev.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | freeboard 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 51 | 52 | 53 |
54 | 55 |
56 |
    57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |

freeboard

65 |
66 |
    67 |
  • 68 |
  • 69 | 70 | 71 | 72 |
  • 73 |
  • 74 |
75 |
76 |
77 |
78 |

DATASOURCES

79 | 80 |
81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 94 | 95 | 102 | 103 | 104 |
NameLast Updated 
92 | 93 | 96 |
    97 |
  • 98 |
  • 99 |
  • 100 |
101 |
105 |
106 | ADD 107 |
108 |
109 |
110 |
111 |
    112 |
  • 113 |
  • 114 |
115 |
    116 |
  • 117 |
  • 118 |
119 |
120 |
121 | 122 |
123 |
124 | 125 |
126 |
    127 |
128 |
129 | 130 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | NETPIE Freeboard 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 40 | 41 | 42 |
43 | 44 |
45 |
    46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |

NETPIE Freeboard

54 |
55 |
    56 |
  • 57 | 58 | 59 |
  • 60 |
  • 61 | 62 | 63 |
  • 64 |
  • 65 | 66 | 67 |
  • 68 |
  • 69 |
70 |
71 |
72 |
73 |

DATASOURCES

74 | 75 |
76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 89 | 90 | 97 | 98 | 99 |
NameLast Updated 
87 | 88 | 91 |
    92 |
  • 93 |
  • 94 |
  • 95 |
96 |
100 |
101 | ADD 102 |
103 |
104 |
105 | 106 | 110 |
111 |
112 |
113 |
    114 |
  • 115 |
  • 116 |
117 |
    118 |
  • 119 |
  • 120 |
121 |
122 |
123 |
124 |
125 | 126 |
127 |
    128 |
129 |
130 | 131 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /js/netpie.global.js: -------------------------------------------------------------------------------- 1 | /* NETPIE global functions */ 2 | 3 | if (typeof globalStore === "undefined") { 4 | globalStore = {}; 5 | } 6 | 7 | function runCode(cmd) { 8 | eval(eval(cmd)); 9 | } 10 | 11 | function randomString(length) { 12 | return Math.round((Math.pow(36, length + 1) - Math.random() * Math.pow(36, length))).toString(36).slice(1); 13 | } 14 | 15 | function toggletheme() { 16 | var stylesheet = document.getElementById('netpie-theme-css'); 17 | if(stylesheet!=null){ 18 | stylesheet.parentNode.removeChild(stylesheet); 19 | np_theme = "default"; 20 | } 21 | else{ 22 | var theme = document.createElement('link'); 23 | theme.id = 'netpie-theme-css'; 24 | theme.href = 'css/netpie.theme.css'; 25 | theme.rel = 'stylesheet'; 26 | document.head.appendChild(theme); 27 | np_theme = "netpie"; 28 | } 29 | saveTheme(); 30 | freeboard.emit('theme_changed'); 31 | } 32 | 33 | if (typeof np_theme === "undefined") { 34 | np_theme = "default"; 35 | } 36 | 37 | function saveTheme(){ 38 | var data = window.localStorage.getItem("netpie.freeboard.dashboard"); 39 | if(data!==null){ 40 | var datajson = JSON.parse(data); 41 | datajson.theme = np_theme; 42 | window.localStorage.setItem("netpie.freeboard.dashboard", JSON.stringify(datajson)); 43 | } 44 | } 45 | 46 | freeboard.on('load_theme',function() { 47 | var stylesheet = document.getElementById('netpie-theme-css'); 48 | var data = window.localStorage.getItem("netpie.freeboard.dashboard"); 49 | var datajson = JSON.parse(data); 50 | if(datajson!==null){ 51 | if(datajson.theme===null || datajson.theme=="default"){ 52 | if(stylesheet!=null){ 53 | stylesheet.parentNode.removeChild(stylesheet); 54 | } 55 | np_theme = "default"; 56 | document.getElementById('theme-toggle').checked = false; 57 | } 58 | if(datajson.theme=="netpie"){ 59 | var theme = document.createElement('link'); 60 | theme.id = 'netpie-theme-css'; 61 | theme.href = 'css/netpie.theme.css'; 62 | theme.rel = 'stylesheet'; 63 | document.head.appendChild(theme); 64 | np_theme = "netpie"; 65 | document.getElementById('theme-toggle').checked = true; 66 | } 67 | } 68 | else{ 69 | np_theme = "default"; 70 | document.getElementById('theme-toggle').checked = false; 71 | } 72 | saveTheme(); 73 | }); 74 | 75 | freeboard.emit('load_theme'); 76 | 77 | -------------------------------------------------------------------------------- /js/netpie.js.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netpie2015/netpie-freeboard/3c5882ed17db12d3a417042932299e47adabe9e2/js/netpie.js.zip -------------------------------------------------------------------------------- /js/netpie.plugin.microgear.js: -------------------------------------------------------------------------------- 1 | /* NETPIE Microgear Freeboard plugin */ 2 | /* Developed by Chavee Issariyapat */ 3 | /* More information about NETPIE please visit https://netpie.io */ 4 | 5 | if (typeof microgear === "undefined") { 6 | microgear = {}; 7 | } 8 | 9 | (function() 10 | { 11 | freeboard.loadDatasourcePlugin({ 12 | "type_name" : "netpie_microgear", 13 | "display_name": "NETPIE Microgear", 14 | "description" : "Connect to NETPIE as a microgear to communicate real-time with other microgears in the same App ID. The microgear of this datasource is referenced by microgear[DATASOURCENAME]", 15 | "external_scripts" : [ 16 | "https://cdn.netpie.io/microgear.js" 17 | ], 18 | "settings" : [ 19 | { 20 | "name" : "appid", 21 | "display_name" : "App ID", 22 | "type" : "text", 23 | "description" : "NETPIE App ID obtained from https://netpie.io/app", 24 | "required" : true 25 | }, 26 | { 27 | "name" : "key", 28 | "display_name" : "Key", 29 | "type" : "text", 30 | "description" : "Key", 31 | "required" : true 32 | }, 33 | { 34 | "name" : "secret", 35 | "display_name" : "Secret", 36 | "type" : "text", 37 | "description" : "Secret", 38 | "type" : "text", 39 | "required" : true 40 | }, 41 | { 42 | "name" : "topics", 43 | "display_name" : "Subscribed Topics", 44 | "type" : "text", 45 | "description" : "Topics of the messages that this datasource will consume, the default is /# which means all messages in this app ID.", 46 | "default_value": "/#", 47 | "required" : false 48 | }, 49 | { 50 | "name" : "onCreatedAction", 51 | "display_name" : "onCreated Action", 52 | "type" : "text", 53 | "description" : "JS code to run after a datasource is created" 54 | }, 55 | { 56 | "name" : "onConnectedAction", 57 | "display_name" : "onConnected Action", 58 | "type" : "text", 59 | "description" : "JS code to run after a microgear datasource is connected to NETPIE" 60 | } 61 | 62 | ], 63 | 64 | newInstance : function(settings, newInstanceCallback, updateCallback) { 65 | newInstanceCallback(new netpieDatasourcePlugin(settings, updateCallback)); 66 | } 67 | }); 68 | 69 | 70 | var netpieDatasourcePlugin = function(settings, updateCallback) { 71 | var self = this; 72 | var currentSettings = settings; 73 | var gconf = { 74 | key: settings.key, 75 | secret: settings.secret 76 | } 77 | if (settings.alias) gconf.alias = settings.alias; 78 | 79 | var data = {}; 80 | var aliasList = {}; 81 | 82 | function initSubscribe(toparr, toSub) { 83 | if (toparr && toparr.length>0) { 84 | for (var i=0; i< toparr.length; i++) { 85 | if (toSub) { 86 | self.mg.subscribe(toparr[i]); 87 | } 88 | else { 89 | self.mg.unsubscribe(toparr[i]); 90 | } 91 | } 92 | } 93 | } 94 | 95 | self.updateNow = function() { 96 | 97 | } 98 | 99 | self.onSettingsChanged = function(newSettings) { 100 | 101 | if (currentSettings.name && (currentSettings.name != newSettings.name)) { 102 | var modifiedname = newSettings.name.substring(0,16); 103 | 104 | if (newSettings.name != modifiedname) { 105 | var text = "The datasource name should not be longer than 16 characters otherwise the associative id will be shorten i.e. now the microgear object is referenced by microgear[\""+modifiedname+"\"] and the microgear device alias is trimmed to \""+modifiedname+"\"."; 106 | newSettings.name = modifiedname; 107 | freeboard.showDialog(text, "Warning", "I understand"); 108 | } 109 | 110 | if (microgear[currentSettings.name]) { 111 | delete(microgear[currentSettings.name]); 112 | } 113 | microgear[newSettings.name] = self.mg; 114 | 115 | self.mg.setAlias(newSettings.name); 116 | } 117 | 118 | if (currentSettings.topics != newSettings.topics) { 119 | initSubscribe(currentSettings.topics.trim().split(','), false); 120 | initSubscribe(newSettings.topics.trim().split(','), true); 121 | } 122 | 123 | if (currentSettings.appid != newSettings.appid || currentSettings.key != newSettings.key || currentSettings.secret != newSettings.secret) { 124 | freeboard.showDialog("Reconfigure AppID, Key or Secret needs a page reloading. Make sure you save the current configuration before processding.", "Warning", "OK", "CANCEL", function() { 125 | location.reload(true); 126 | }) 127 | } 128 | currentSettings = newSettings; 129 | } 130 | 131 | self.onDispose = function() { 132 | delete(self.mg); 133 | } 134 | 135 | self.mg = Microgear.create(gconf); 136 | 137 | if(settings.name !== undefined){ 138 | settings.name = settings.name.replace(' ','_').substring(0,16); 139 | } 140 | 141 | microgear[settings.name] = self.mg; 142 | 143 | self.mg.on('message', function(topic,msg) { 144 | if (topic && msg) { 145 | data[topic] = msg; 146 | updateCallback(data); 147 | } 148 | }); 149 | 150 | self.mg.on('present', function(m) { 151 | var mtoken = m.gear; 152 | var aobj = { 153 | token : mtoken 154 | }; 155 | var found = false; 156 | if (typeof(aliasList[m.alias]) != 'undefined') { 157 | for (var k=0; k 0) { 227 | clearInterval(timer); 228 | eval(settings.onConnectedAction); 229 | } 230 | },200); 231 | } 232 | 233 | if (typeof(onConnectedHandler) != 'undefined') { 234 | onConnectedHandler(settings.name); 235 | } 236 | }) 237 | 238 | self.mg.on('disconnected', function() { 239 | aliasList = {}; 240 | data['alias'] = aliasList; 241 | updateCallback(data); 242 | }); 243 | 244 | if (settings.onCreatedAction) { 245 | eval(settings.onCreatedAction); 246 | } 247 | 248 | self.mg.connect(settings.appid, function(){ 249 | 250 | }); 251 | } 252 | }()); 253 | -------------------------------------------------------------------------------- /js/netpie.widget.button.js: -------------------------------------------------------------------------------- 1 | /* NETPIE widget plugin for Freeboard */ 2 | /* Developed by Chavee Issariyapat */ 3 | /* More information about NETPIE please visit https://netpie.io */ 4 | 5 | (function() { 6 | var bcolor = {red:["#FFF","#e74c3c"],green:["#FFF","#2ecc71"],blue:["#FFF","#3498db"],yellow:["#FFF","#f1c40f"],white:["#454545","#ecf0f1"],grey:["#FFF","#bdc3c7"]}; 7 | 8 | freeboard.loadWidgetPlugin({ 9 | "type_name" : "Button", 10 | "display_name": "Button", 11 | "description" : "A simple button widget that can perform Javascript action.", 12 | "fill_size" : false, 13 | "settings" : [ 14 | { 15 | "name" : "caption", 16 | "display_name": "Button Caption", 17 | "type" : "text" 18 | }, 19 | { 20 | "name" : "text", 21 | "display_name": "Label Text", 22 | "type" : "text" 23 | }, 24 | { 25 | "name" : "color", 26 | "display_name": "Button Color", 27 | "type" : "option", 28 | "options" : [ 29 | { 30 | "name" : "Red", 31 | "value": "red" 32 | }, 33 | { 34 | "name" : "Green", 35 | "value": "green" 36 | }, 37 | { 38 | "name" : "Blue", 39 | "value": "blue" 40 | }, 41 | { 42 | "name" : "Yellow", 43 | "value": "yellow" 44 | }, 45 | { 46 | "name" : "White", 47 | "value": "white" 48 | }, 49 | { 50 | "name" : "Grey", 51 | "value": "grey" 52 | } 53 | 54 | ] 55 | }, 56 | { 57 | "name" : "onClick", 58 | "display_name": "onClick action", 59 | "type" : "calculated", 60 | "description" : "Add some Javascript here. You can chat and publish with a datasource's microgear like this : microgear[\"mygear\"].chat(\"mylamp\",\"ON\"), where \"mygear\" is a datasource name." 61 | }, 62 | { 63 | "name" : "onCreatedAction", 64 | "display_name" : "onCreated Action", 65 | "type" : "text", 66 | "description" : "JS code to run after a button is created" 67 | } 68 | 69 | ], 70 | newInstance : function(settings, newInstanceCallback) { 71 | newInstanceCallback(new buttonWidgetPlugin(settings)); 72 | } 73 | }); 74 | 75 | var buttonWidgetPlugin = function(settings) { 76 | var self = this; 77 | var currentSettings = settings; 78 | 79 | self.widgetID = randomString(16); 80 | 81 | var buttonElement = $(""); 82 | var textElement = $("
"+(settings.text?settings.text:"")+"
"); 83 | 84 | globalStore[self.widgetID] = {}; 85 | globalStore[self.widgetID]['onClick'] = settings.onClick; 86 | 87 | function updateButtonColor(color) { 88 | if (bcolor[color]) { 89 | buttonElement.css({ 90 | "color" : bcolor[color][0], 91 | "background-color" : bcolor[color][1] 92 | }); 93 | } 94 | } 95 | 96 | updateButtonColor(settings.color); 97 | 98 | self.render = function(containerElement) { 99 | $(containerElement).append(buttonElement).append(textElement); 100 | } 101 | 102 | self.getHeight = function() { 103 | return 1; 104 | } 105 | 106 | self.onSettingsChanged = function(newSettings) { 107 | currentSettings = newSettings; 108 | document.getElementById(self.widgetID).value = newSettings.caption; 109 | updateButtonColor(newSettings.color); 110 | textElement.text(newSettings.text?newSettings.text:""); 111 | globalStore[self.widgetID]['onClick'] = newSettings.onClick; 112 | } 113 | 114 | self.onCalculatedValueChanged = function(settingName, newValue) { 115 | if(settingName == "caption") { 116 | $(buttonElement).val(newValue); 117 | } 118 | } 119 | 120 | self.onDispose = function() { 121 | } 122 | 123 | if (settings.onCreatedAction) { 124 | var timer = setInterval(function() { 125 | if (Object.getOwnPropertyNames(microgear).length > 0) { 126 | clearInterval(timer); 127 | eval(settings.onCreatedAction); 128 | } 129 | },200); 130 | } 131 | } 132 | }()); 133 | -------------------------------------------------------------------------------- /js/netpie.widget.feedview.js: -------------------------------------------------------------------------------- 1 | /* NETPIE widget plugin for Freeboard */ 2 | /* Developed by Chavee Issariyapat */ 3 | /* More information about NETPIE please visit https://netpie.io */ 4 | 5 | if (typeof feedview === "undefined") { 6 | feedview = []; 7 | } 8 | 9 | (function() { 10 | 11 | freeboard.loadWidgetPlugin({ 12 | "type_name" : "FeedView", 13 | "display_name": "FeedView", 14 | "description" : "", 15 | "fill_size" : true, 16 | "external_scripts" : [ 17 | "js/netpie.feedview.js" 18 | ], 19 | "settings" : [ 20 | { 21 | "name" : "title", 22 | "display_name": "Title", 23 | "type" : "text" 24 | }, 25 | { 26 | "name" : "datasource", 27 | "display_name" : "Data Source", 28 | "type" : "calculated", 29 | "description" : "" 30 | }, 31 | { 32 | name: "filter", 33 | display_name: "Filter", 34 | type: "text", 35 | "description" : "Data fields separated with comma e.g. temp,humid,light. Blank means display all fields." 36 | }, 37 | { 38 | name: "type", 39 | display_name: "Type of Chart", 40 | type: "option", 41 | options:[ 42 | { 43 | name: "Line", 44 | value: "line" 45 | }, 46 | { 47 | name: "Step", 48 | value: "step" 49 | } 50 | ] 51 | }, 52 | { 53 | name: "xaxis", 54 | display_name: "X axis title", 55 | type: "text", 56 | }, 57 | { 58 | name: "yaxis", 59 | display_name: "Y axis title", 60 | type: "text", 61 | }, 62 | { 63 | name: "yzero", 64 | display_name: "begin at 0", 65 | type: "boolean", 66 | }, 67 | { 68 | name: "color", 69 | display_name: "Line Colors", 70 | type: "text", 71 | default_value: "", 72 | "description": "enter the color set separated by comma e.g. #ff0000,#00ff00,#0000ff or leave blank for the default color set" 73 | }, 74 | { 75 | name: "marker", 76 | display_name: "Maker", 77 | type: "boolean", 78 | default_value: true 79 | }, 80 | { 81 | name: "multipleaxis", 82 | display_name: "Multiple Axis", 83 | type: "boolean", 84 | default_value: true 85 | }, 86 | { 87 | name: "autogap", 88 | display_name: "Auto Gap", 89 | type: "boolean", 90 | default_value: false 91 | }, 92 | { 93 | name: "height_block", 94 | display_name: "Height Blocks", 95 | type: "option", 96 | options:[ 97 | { 98 | name: "4", 99 | value: "240" 100 | }, 101 | { 102 | name: "5", 103 | value: "300" 104 | }, 105 | { 106 | name: "6", 107 | value: "360" 108 | }, 109 | { 110 | name: "7", 111 | value: "420" 112 | }, 113 | { 114 | name: "8", 115 | value: "480" 116 | }, 117 | { 118 | name: "9", 119 | value: "540" 120 | }, 121 | { 122 | name: "10", 123 | value: "600" 124 | } 125 | ] 126 | }, 127 | 128 | ], 129 | newInstance : function(settings, newInstanceCallback) { 130 | newInstanceCallback(new feedviewWidgetPlugin(settings)); 131 | } 132 | }); 133 | 134 | var feedviewWidgetPlugin = function(settings) { 135 | var self = this; 136 | var sizeWidth = {"240":"4","300":"5","360":"6","420":"7","480":"8","540":"9","600":"10"}; 137 | self.widgetID = randomString(16); 138 | var currentSettings = settings; 139 | var feedviewElement = $("
"); 140 | self.render = function(containerElement) { 141 | currentSettings.height = sizeWidth[currentSettings.height_block]; 142 | $(containerElement).append(feedviewElement); 143 | feedviewElement.css({ 144 | height:currentSettings.height_block+"px", 145 | }); 146 | } 147 | 148 | this.getHeight = function () { 149 | if(currentSettings.height===undefined){ 150 | currentSettings.height = 4; 151 | } 152 | return Number(currentSettings.height); 153 | } 154 | 155 | self.onSettingsChanged = function(newSettings) { 156 | currentSettings = newSettings; 157 | insertFeedView(); 158 | } 159 | 160 | self.onCalculatedValueChanged = function(settingName, newValue) { 161 | self.valuejson = newValue; 162 | insertFeedView(); 163 | } 164 | 165 | self.onDispose = function() { 166 | for (var i = feedview.length - 1; i >= 0; i--) { 167 | if(self.widgetID==feedview[i].id){ 168 | check = true; 169 | index = i; 170 | } 171 | } 172 | if(!check){ 173 | feedview.remove(i); 174 | } 175 | } 176 | 177 | //this.onSettingsChanged(settings); 178 | 179 | freeboard.on('theme_changed',function() { 180 | updateChart('chart'+self.widgetID,self.valuejson,self.option); 181 | }); 182 | 183 | var insertFeedView =function() { 184 | currentSettings.height = sizeWidth[currentSettings.height_block]; 185 | $("#"+'chart'+self.widgetID).css({ 186 | height:currentSettings.height_block+"px", 187 | }); 188 | if(self.valuejson!==undefined){ 189 | self.option = { 190 | title : currentSettings.title, 191 | xaxis : currentSettings.xaxis, 192 | yaxis : currentSettings.yaxis, 193 | multipleaxis : currentSettings.multipleaxis, 194 | yzero:currentSettings.yzero, 195 | color:currentSettings.color, 196 | type : currentSettings.type, //line,step 197 | marker : currentSettings.marker, //true,false 198 | filter : currentSettings.filter, 199 | autogap : currentSettings.autogap 200 | } 201 | // jQuery(window).ready(function() { 202 | updateChart('chart'+self.widgetID,self.valuejson,self.option); 203 | // }); 204 | } 205 | } 206 | } 207 | }()); 208 | -------------------------------------------------------------------------------- /js/netpie.widget.slider.js: -------------------------------------------------------------------------------- 1 | /* NETPIE widget plugin for Freeboard */ 2 | /* Developed by Chavee Issariyapat */ 3 | /* More information about NETPIE please visit https://netpie.io */ 4 | 5 | if (typeof sliderObject == "undefined") { 6 | sliderObject = {}; 7 | } 8 | 9 | (function() { 10 | var bcolor = {red:["#FFF","#e74c3c"],green:["#FFF","#2ecc71"],blue:["#FFF","#3498db"],yellow:["#FFF","#f1c40f"],white:["#454545","#ecf0f1"],grey:["#FFF","#bdc3c7"]}; 11 | 12 | $('head').append(''); 13 | 14 | freeboard.loadWidgetPlugin({ 15 | "type_name" : "Slider", 16 | "display_name": "Slider", 17 | "description" : "A slider widget that can perform Javascript action.", 18 | "fill_size" : false, 19 | "external_scripts" : ["plugins/thirdparty/rangeslider.js"], 20 | "settings" : [ 21 | { 22 | "name" : "caption", 23 | "display_name": "Slider Caption", 24 | "type" : "text" 25 | }, 26 | { 27 | "name" : "color", 28 | "display_name": "Filled Color", 29 | "type" : "option", 30 | "options" : [ 31 | { 32 | "name" : "Red", 33 | "value": "red" 34 | }, 35 | { 36 | "name" : "Green", 37 | "value": "green" 38 | }, 39 | { 40 | "name" : "Blue", 41 | "value": "blue" 42 | }, 43 | { 44 | "name" : "Yellow", 45 | "value": "yellow" 46 | }, 47 | { 48 | "name" : "White", 49 | "value": "white" 50 | }, 51 | { 52 | "name" : "Grey", 53 | "value": "grey" 54 | } 55 | 56 | ], 57 | "default_value" : "grey" 58 | }, 59 | { 60 | "name" : "showvalue", 61 | "display_name" : "Display value", 62 | "type" : "boolean", 63 | "default_value" : 1 64 | }, 65 | { 66 | "name" : "min", 67 | "display_name" : "Min Value", 68 | "type" : "text", 69 | "default_value" : 0 70 | }, 71 | { 72 | "name" : "max", 73 | "display_name" : "Max Value", 74 | "type" : "text", 75 | "default_value" : 100 76 | }, 77 | { 78 | "name" : "step", 79 | "display_name" : "Step", 80 | "type" : "text", 81 | "default_value" : 1 82 | }, 83 | { 84 | "name" : "initialvalue", 85 | "display_name" : "Initial Value", 86 | "type" : "text", 87 | "default_value" : "0", 88 | "description" : "The default value set only the first time the widget is loaded." 89 | }, 90 | { 91 | "name" : "autovaluesource", 92 | "display_name" : "Auto Updated Value", 93 | "type" : "calculated", 94 | "description" : "Slider will be updated upon the change of variables (e.g. other data sources)." 95 | }, 96 | { 97 | "name" : "onStart", 98 | "display_name": "onStart action", 99 | "type" : "calculated", 100 | "description" : "Add some Javascript here. You can access to a slider attribute using variables 'value' and 'percent'." 101 | }, 102 | { 103 | "name" : "onSlide", 104 | "display_name": "onSlide action", 105 | "type" : "calculated", 106 | "description" : "Add some Javascript here. You can access to a slider attribute using variables 'value' and 'percent'." 107 | }, 108 | { 109 | "name" : "onStop", 110 | "display_name": "onStop action", 111 | "type" : "calculated", 112 | "description" : "Add some Javascript here. You can access to a slider attribute using variables 'value' and 'percent'." 113 | }, 114 | { 115 | "name" : "onCreatedAction", 116 | "display_name" : "onCreated Action", 117 | "type" : "text", 118 | "description" : "JS code to run after a button is created" 119 | } 120 | 121 | ], 122 | newInstance : function(settings, newInstanceCallback) { 123 | newInstanceCallback(new sliderWidgetPlugin(settings)); 124 | } 125 | }); 126 | 127 | var sliderWidgetPlugin = function(settings) { 128 | var self = this; 129 | var currentSettings = settings; 130 | 131 | self.widgetID = randomString(16); 132 | 133 | var sliderElement = $(""); 134 | self.autoValue = 0; 135 | 136 | var t = settings.initialvalue || 0; 137 | if (t > settings.max) t = settings.max; 138 | else if (t < settings.min) t = settings.min; 139 | 140 | var textElement = $(""+(settings.caption?settings.caption:"")+""); 141 | var valueElement = $(""+t+""); 142 | 143 | if (settings.showvalue) valueElement.show(); 144 | else valueElement.hide(); 145 | 146 | self.linkAutoValue = (settings.autovaluesource && settings.autovaluesource.length > 0); 147 | 148 | globalStore[self.widgetID] = {}; 149 | 150 | globalStore[self.widgetID]['onStart'] = settings.onStart; 151 | globalStore[self.widgetID]['onStop'] = settings.onStop; 152 | globalStore[self.widgetID]['onSlide'] = settings.onSlide; 153 | 154 | function updateSliderColor(color) { 155 | if (bcolor[color]) { 156 | if (document.getElementById(self.widgetID)) { 157 | sliderObject[self.widgetID].setFillColor(bcolor[color][1]); 158 | /* 159 | var c = hexToRgb(bcolor[color][1]); 160 | sliderObject[self.widgetID].setBackgroundColor('rgba('+c.r+','+c.g+','+c.b+',0.14)'); 161 | */ 162 | } 163 | } 164 | } 165 | 166 | self.render = function(containerElement) { 167 | $(containerElement).append(textElement).append(valueElement).append(sliderElement); 168 | 169 | self.lastSlideCallback = 0; 170 | self.nextSlideCallbackTimer = 0; 171 | self.maxCallbackDuration = 200; //ms 172 | 173 | (function () { 174 | var elements = document.getElementById(self.widgetID); 175 | // Basic rangeSlider initialization 176 | sliderObject[self.widgetID] = rangeSlider.create(elements, { 177 | // Callback function 178 | onInit: function () { 179 | self.controlling = false; 180 | }, 181 | 182 | // Callback function 183 | onSlideStart: function (value, percent, position) { 184 | self.controlling = true; 185 | valueElement.text(value); 186 | if (globalStore[self.widgetID]['onStart']) 187 | eval('var value='+value+'; var percent='+percent+';'+globalStore[self.widgetID]['onStart']); 188 | //console.info('onSlideStart', 'value: ' + value, 'percent: ' + percent, 'position: ' + position); 189 | }, 190 | 191 | // Callback function 192 | onSlide: function (value, percent, position) { 193 | self.controlling = true; 194 | valueElement.text(value); 195 | if (globalStore[self.widgetID]['onSlide']) { 196 | if (Date.now() - self.lastSlideCallback > self.maxCallbackDuration) { 197 | eval('var value='+value+'; var percent='+percent+';'+globalStore[self.widgetID]['onSlide']); 198 | self.lastSlideCallback = Date.now(); 199 | } 200 | else { 201 | if (self.nextSlideCallbackTimer==0) { 202 | self.nextSlideCallbackTimer = setTimeout( function() { 203 | eval('var value='+sliderObject[self.widgetID].value+'; var percent='+percent+';'+globalStore[self.widgetID]['onSlide']); 204 | self.lastSlideCallback = Date.now(); 205 | self.nextSlideCallbackTimer=0; 206 | },self.maxCallbackDuration-(Date.now()-self.lastSlideCallback)); 207 | } 208 | } 209 | } 210 | }, 211 | 212 | // Callback function 213 | onSlideEnd: function (value, percent, position) { 214 | valueElement.text(value); 215 | if (globalStore[self.widgetID]['onStop']) 216 | eval('var value='+value+'; var percent='+percent+';'+globalStore[self.widgetID]['onStop']); 217 | 218 | if (self.linkAutoValue) { 219 | setTimeout(function() { 220 | sliderObject[self.widgetID].update({value: self.autoValue}); 221 | valueElement.text(sliderObject[self.widgetID].value); 222 | self.controlling = false; 223 | },500); 224 | } 225 | else { 226 | self.controlling = false; 227 | } 228 | } 229 | }); 230 | })(); 231 | 232 | updateSliderColor(settings.color); 233 | } 234 | 235 | self.getHeight = function() { 236 | return 1; 237 | } 238 | 239 | self.onSettingsChanged = function(newSettings) { 240 | currentSettings = newSettings; 241 | updateSliderColor(newSettings.color); 242 | textElement.text(newSettings.caption?newSettings.caption:""); 243 | 244 | if (newSettings.showvalue) valueElement.show(); 245 | else valueElement.hide(); 246 | 247 | globalStore[self.widgetID]['onStart'] = newSettings.onStart; 248 | globalStore[self.widgetID]['onStop'] = newSettings.onStop; 249 | globalStore[self.widgetID]['onSlide'] = newSettings.onSlide; 250 | 251 | var oldvalue = sliderObject[self.widgetID].value; 252 | 253 | /* have to update in 2 steps */ 254 | sliderObject[self.widgetID].update({min: Number(newSettings.min||0), max: Number(newSettings.max||100), step: Number(newSettings.step||1)}); 255 | sliderObject[self.widgetID].update({value: oldvalue}); 256 | 257 | valueElement.text(sliderObject[self.widgetID].value); 258 | 259 | self.linkAutoValue = (newSettings.autovaluesource && newSettings.autovaluesource.length > 0); 260 | if (self.linkAutoValue) { 261 | sliderObject[self.widgetID].update({value: self.autoValue}); 262 | valueElement.text(sliderObject[self.widgetID].value); 263 | } 264 | } 265 | 266 | self.onCalculatedValueChanged = function(settingName, newValue) { 267 | if(settingName == "autovaluesource") { 268 | if (self.controlling) { 269 | self.autoValue = newValue; 270 | } 271 | else { 272 | self.autoValue = newValue; 273 | sliderObject[self.widgetID].update({value: newValue}); 274 | valueElement.text(sliderObject[self.widgetID].value); 275 | } 276 | } 277 | } 278 | 279 | self.onDispose = function() { 280 | } 281 | 282 | if (settings.onCreatedAction) { 283 | var timer = setInterval(function() { 284 | if (Object.getOwnPropertyNames(microgear).length > 0) { 285 | clearInterval(timer); 286 | eval(settings.onCreatedAction); 287 | } 288 | },200); 289 | } 290 | } 291 | 292 | 293 | 294 | }()); 295 | -------------------------------------------------------------------------------- /js/netpie.widget.toggle.js: -------------------------------------------------------------------------------- 1 | /* NETPIE widget plugin for Freeboard */ 2 | /* Developed by Chavee Issariyapat */ 3 | /* More information about NETPIE please visit https://netpie.io */ 4 | 5 | (function() { 6 | 7 | freeboard.loadWidgetPlugin({ 8 | "type_name" : "Toggle", 9 | "display_name": "Toggle", 10 | "description" : "A simple toggle widget that can perform Javascript action.", 11 | "fill_size" : false, 12 | "settings" : [ 13 | { 14 | "name" : "caption", 15 | "display_name": "Toggle Caption", 16 | "type" : "text" 17 | }, 18 | { 19 | "name" : "state", 20 | "display_name" : "Toggle State", 21 | "type" : "calculated", 22 | "description" : "Add a condition to switch a toggle state here. Otherwise it just toggle by click." 23 | }, 24 | { 25 | "name" : "ontext", 26 | "display_name" : "On Text", 27 | "type" : "text", 28 | "default_value" : "ON" 29 | }, 30 | { 31 | "name" : "offtext", 32 | "display_name" : "Off Text", 33 | "type" : "text", 34 | "default_value" : "OFF" 35 | }, 36 | { 37 | "name" : "onaction", 38 | "display_name" : "onToggleOn Action", 39 | "type" : "text", 40 | "description" : "JS code to run when a toggle is switched to ON" 41 | }, 42 | { 43 | "name" : "offaction", 44 | "display_name" : "onToggleOff Action", 45 | "type" : "text", 46 | "description" : "JS code to run when a toggle is switched to OFF" 47 | }, 48 | { 49 | "name" : "onCreatedAction", 50 | "display_name" : "onCreated Action", 51 | "type" : "text", 52 | "description" : "JS code to run after a toggle is created" 53 | } 54 | 55 | ], 56 | newInstance : function(settings, newInstanceCallback) { 57 | newInstanceCallback(new toggleWidgetPlugin(settings)); 58 | } 59 | }); 60 | 61 | var toggleWidgetPlugin = function(settings) { 62 | var self = this; 63 | self.widgetID = randomString(16); 64 | 65 | var currentSettings = settings; 66 | var toggleElement = $("
"+(settings.caption||"")+"
"); 67 | 68 | globalStore[self.widgetID] = {}; 69 | globalStore[self.widgetID]['onaction'] = settings.onaction; 70 | globalStore[self.widgetID]['offaction'] = settings.offaction; 71 | globalStore[self.widgetID]['statesource'] = settings.state; 72 | 73 | self.render = function(containerElement) { 74 | $(containerElement).append(toggleElement); 75 | } 76 | 77 | self.getHeight = function() { 78 | return 1; 79 | } 80 | 81 | self.onSettingsChanged = function(newSettings) { 82 | currentSettings = newSettings; 83 | 84 | globalStore[self.widgetID]['onaction'] = newSettings.onaction; 85 | globalStore[self.widgetID]['offaction'] = newSettings.offaction; 86 | globalStore[self.widgetID]['statesource'] = newSettings.state; 87 | $('#'+self.widgetID+'_inner').attr('ontext',newSettings.ontext||''); 88 | $('#'+self.widgetID+'_inner').attr('offtext',newSettings.offtext||''); 89 | document.getElementById(self.widgetID+'_toggleText').innerHTML = newSettings.caption||''; 90 | } 91 | 92 | self.onCalculatedValueChanged = function(settingName, newValue) { 93 | if (settingName == 'state') { 94 | document.getElementById(self.widgetID).checked = newValue; 95 | } 96 | } 97 | 98 | self.onDispose = function() { 99 | } 100 | 101 | if (settings.onCreatedAction) { 102 | var timer = setInterval(function() { 103 | if (Object.getOwnPropertyNames(microgear).length > 0) { 104 | clearInterval(timer); 105 | eval(settings.onCreatedAction); 106 | } 107 | },200); 108 | } 109 | 110 | } 111 | 112 | }()); 113 | -------------------------------------------------------------------------------- /lib/css/thirdparty/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | } 8 | .CodeMirror-scroll { 9 | /* Set scrolling behaviour here */ 10 | overflow: auto; 11 | } 12 | 13 | /* PADDING */ 14 | 15 | .CodeMirror-lines { 16 | padding: 4px 0; /* Vertical padding around content */ 17 | } 18 | .CodeMirror pre { 19 | padding: 0 4px; /* Horizontal padding of content */ 20 | } 21 | 22 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 23 | background-color: white; /* The little square between H and V scrollbars */ 24 | } 25 | 26 | /* GUTTER */ 27 | 28 | .CodeMirror-gutters { 29 | border-right: 1px solid #ddd; 30 | background-color: #f7f7f7; 31 | white-space: nowrap; 32 | } 33 | .CodeMirror-linenumbers {} 34 | .CodeMirror-linenumber { 35 | padding: 0 3px 0 5px; 36 | min-width: 20px; 37 | text-align: right; 38 | color: #999; 39 | -moz-box-sizing: content-box; 40 | box-sizing: content-box; 41 | } 42 | 43 | /* CURSOR */ 44 | 45 | .CodeMirror div.CodeMirror-cursor { 46 | border-left: 1px solid black; 47 | } 48 | /* Shown when moving in bi-directional text */ 49 | .CodeMirror div.CodeMirror-secondarycursor { 50 | border-left: 1px solid silver; 51 | } 52 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { 53 | width: auto; 54 | border: 0; 55 | background: #7e7; 56 | } 57 | /* Can style cursor different in overwrite (non-insert) mode */ 58 | div.CodeMirror-overwrite div.CodeMirror-cursor {} 59 | 60 | .cm-tab { display: inline-block; } 61 | 62 | .CodeMirror-ruler { 63 | border-left: 1px solid #ccc; 64 | position: absolute; 65 | } 66 | 67 | /* DEFAULT THEME */ 68 | 69 | .cm-s-default .cm-keyword {color: #708;} 70 | .cm-s-default .cm-atom {color: #219;} 71 | .cm-s-default .cm-number {color: #164;} 72 | .cm-s-default .cm-def {color: #00f;} 73 | .cm-s-default .cm-variable, 74 | .cm-s-default .cm-punctuation, 75 | .cm-s-default .cm-property, 76 | .cm-s-default .cm-operator {} 77 | .cm-s-default .cm-variable-2 {color: #05a;} 78 | .cm-s-default .cm-variable-3 {color: #085;} 79 | .cm-s-default .cm-comment {color: #a50;} 80 | .cm-s-default .cm-string {color: #a11;} 81 | .cm-s-default .cm-string-2 {color: #f50;} 82 | .cm-s-default .cm-meta {color: #555;} 83 | .cm-s-default .cm-qualifier {color: #555;} 84 | .cm-s-default .cm-builtin {color: #30a;} 85 | .cm-s-default .cm-bracket {color: #997;} 86 | .cm-s-default .cm-tag {color: #170;} 87 | .cm-s-default .cm-attribute {color: #00c;} 88 | .cm-s-default .cm-header {color: blue;} 89 | .cm-s-default .cm-quote {color: #090;} 90 | .cm-s-default .cm-hr {color: #999;} 91 | .cm-s-default .cm-link {color: #00c;} 92 | 93 | .cm-negative {color: #d44;} 94 | .cm-positive {color: #292;} 95 | .cm-header, .cm-strong {font-weight: bold;} 96 | .cm-em {font-style: italic;} 97 | .cm-link {text-decoration: underline;} 98 | 99 | .cm-s-default .cm-error {color: #f00;} 100 | .cm-invalidchar {color: #f00;} 101 | 102 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 103 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 104 | .CodeMirror-activeline-background {background: #e8f2ff;} 105 | 106 | /* STOP */ 107 | 108 | /* The rest of this file contains styles related to the mechanics of 109 | the editor. You probably shouldn't touch them. */ 110 | 111 | .CodeMirror { 112 | line-height: 1; 113 | position: relative; 114 | overflow: hidden; 115 | background: white; 116 | color: black; 117 | } 118 | 119 | .CodeMirror-scroll { 120 | /* 30px is the magic margin used to hide the element's real scrollbars */ 121 | /* See overflow: hidden in .CodeMirror */ 122 | margin-bottom: -30px; margin-right: -30px; 123 | padding-bottom: 30px; 124 | height: 100%; 125 | outline: none; /* Prevent dragging from highlighting the element */ 126 | position: relative; 127 | -moz-box-sizing: content-box; 128 | box-sizing: content-box; 129 | } 130 | .CodeMirror-sizer { 131 | position: relative; 132 | border-right: 30px solid transparent; 133 | -moz-box-sizing: content-box; 134 | box-sizing: content-box; 135 | } 136 | 137 | /* The fake, visible scrollbars. Used to force redraw during scrolling 138 | before actuall scrolling happens, thus preventing shaking and 139 | flickering artifacts. */ 140 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 141 | position: absolute; 142 | z-index: 6; 143 | display: none; 144 | } 145 | .CodeMirror-vscrollbar { 146 | right: 0; top: 0; 147 | overflow-x: hidden; 148 | overflow-y: scroll; 149 | } 150 | .CodeMirror-hscrollbar { 151 | bottom: 0; left: 0; 152 | overflow-y: hidden; 153 | overflow-x: scroll; 154 | } 155 | .CodeMirror-scrollbar-filler { 156 | right: 0; bottom: 0; 157 | } 158 | .CodeMirror-gutter-filler { 159 | left: 0; bottom: 0; 160 | } 161 | 162 | .CodeMirror-gutters { 163 | position: absolute; left: 0; top: 0; 164 | padding-bottom: 30px; 165 | z-index: 3; 166 | } 167 | .CodeMirror-gutter { 168 | white-space: normal; 169 | height: 100%; 170 | -moz-box-sizing: content-box; 171 | box-sizing: content-box; 172 | padding-bottom: 30px; 173 | margin-bottom: -32px; 174 | display: inline-block; 175 | /* Hack to make IE7 behave */ 176 | *zoom:1; 177 | *display:inline; 178 | } 179 | .CodeMirror-gutter-elt { 180 | position: absolute; 181 | cursor: default; 182 | z-index: 4; 183 | } 184 | 185 | .CodeMirror-lines { 186 | cursor: text; 187 | } 188 | .CodeMirror pre { 189 | /* Reset some styles that the rest of the page might have set */ 190 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 191 | border-width: 0; 192 | background: transparent; 193 | font-family: inherit; 194 | font-size: inherit; 195 | margin: 0; 196 | white-space: pre; 197 | word-wrap: normal; 198 | line-height: inherit; 199 | color: inherit; 200 | z-index: 2; 201 | position: relative; 202 | overflow: visible; 203 | } 204 | .CodeMirror-wrap pre { 205 | word-wrap: break-word; 206 | white-space: pre-wrap; 207 | word-break: normal; 208 | } 209 | 210 | .CodeMirror-linebackground { 211 | position: absolute; 212 | left: 0; right: 0; top: 0; bottom: 0; 213 | z-index: 0; 214 | } 215 | 216 | .CodeMirror-linewidget { 217 | position: relative; 218 | z-index: 2; 219 | overflow: auto; 220 | } 221 | 222 | .CodeMirror-widget {} 223 | 224 | .CodeMirror-wrap .CodeMirror-scroll { 225 | overflow-x: hidden; 226 | } 227 | 228 | .CodeMirror-measure { 229 | position: absolute; 230 | width: 100%; 231 | height: 0; 232 | overflow: hidden; 233 | visibility: hidden; 234 | } 235 | .CodeMirror-measure pre { position: static; } 236 | 237 | .CodeMirror div.CodeMirror-cursor { 238 | position: absolute; 239 | border-right: none; 240 | width: 0; 241 | } 242 | 243 | div.CodeMirror-cursors { 244 | visibility: hidden; 245 | position: relative; 246 | z-index: 1; 247 | } 248 | .CodeMirror-focused div.CodeMirror-cursors { 249 | visibility: visible; 250 | } 251 | 252 | .CodeMirror-selected { background: #d9d9d9; } 253 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 254 | .CodeMirror-crosshair { cursor: crosshair; } 255 | 256 | .cm-searching { 257 | background: #ffa; 258 | background: rgba(255, 255, 0, .4); 259 | } 260 | 261 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 262 | .CodeMirror span { *vertical-align: text-bottom; } 263 | 264 | /* Used to force a border model for a node */ 265 | .cm-force-border { padding-right: .1px; } 266 | 267 | @media print { 268 | /* Hide the cursor when printing */ 269 | .CodeMirror div.CodeMirror-cursors { 270 | visibility: hidden; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /lib/css/thirdparty/jquery.gridster.min.css: -------------------------------------------------------------------------------- 1 | /*! gridster.js - v0.1.0 - 2013-06-14 - * http://gridster.net/ - Copyright (c) 2013 ducksboard; Licensed MIT */ 2 | .gridster{position:relative}.gridster>*{margin:0 auto;-webkit-transition:height .4s;-moz-transition:height .4s;-o-transition:height .4s;-ms-transition:height .4s;transition:height .4s}.gridster .gs_w{z-index:2;position:absolute}.ready .gs_w:not(.preview-holder){-webkit-transition:opacity .3s,left .3s,top .3s;-moz-transition:opacity .3s,left .3s,top .3s;-o-transition:opacity .3s,left .3s,top .3s;transition:opacity .3s,left .3s,top .3s}.ready .gs_w:not(.preview-holder){-webkit-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-moz-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;-o-transition:opacity .3s,left .3s,top .3s,width .3s,height .3s;transition:opacity .3s,left .3s,top .3s,width .3s,height .3s}.gridster .preview-holder{z-index:1;position:absolute;background-color:#fff;border-color:#fff;opacity:.3}.gridster .player-revert{z-index:10!important;-webkit-transition:left .3s,top .3s!important;-moz-transition:left .3s,top .3s!important;-o-transition:left .3s,top .3s!important;transition:left .3s,top .3s!important}.gridster .dragging{z-index:10!important;-webkit-transition:all 0s!important;-moz-transition:all 0s!important;-o-transition:all 0s!important;transition:all 0s!important} -------------------------------------------------------------------------------- /lib/js/freeboard/DatasourceModel.js: -------------------------------------------------------------------------------- 1 | DatasourceModel = function(theFreeboardModel, datasourcePlugins) { 2 | var self = this; 3 | 4 | function disposeDatasourceInstance() 5 | { 6 | if(!_.isUndefined(self.datasourceInstance)) 7 | { 8 | if(_.isFunction(self.datasourceInstance.onDispose)) 9 | { 10 | self.datasourceInstance.onDispose(); 11 | } 12 | 13 | self.datasourceInstance = undefined; 14 | } 15 | } 16 | 17 | this.name = ko.observable(); 18 | this.latestData = ko.observable(); 19 | this.settings = ko.observable({}); 20 | this.settings.subscribe(function(newValue) 21 | { 22 | if(!_.isUndefined(self.datasourceInstance) && _.isFunction(self.datasourceInstance.onSettingsChanged)) 23 | { 24 | self.datasourceInstance.onSettingsChanged(newValue); 25 | } 26 | }); 27 | 28 | this.updateCallback = function(newData) 29 | { 30 | theFreeboardModel.processDatasourceUpdate(self, newData); 31 | 32 | self.latestData(newData); 33 | 34 | var now = new Date(); 35 | self.last_updated(now.toLocaleTimeString()); 36 | } 37 | 38 | this.type = ko.observable(); 39 | this.type.subscribe(function(newValue) 40 | { 41 | disposeDatasourceInstance(); 42 | 43 | if((newValue in datasourcePlugins) && _.isFunction(datasourcePlugins[newValue].newInstance)) 44 | { 45 | var datasourceType = datasourcePlugins[newValue]; 46 | 47 | function finishLoad() 48 | { 49 | datasourceType.newInstance(self.settings(), function(datasourceInstance) 50 | { 51 | 52 | self.datasourceInstance = datasourceInstance; 53 | datasourceInstance.updateNow(); 54 | 55 | }, self.updateCallback); 56 | } 57 | 58 | // Do we need to load any external scripts? 59 | if(datasourceType.external_scripts) 60 | { 61 | head.js(datasourceType.external_scripts.slice(0), finishLoad); // Need to clone the array because head.js adds some weird functions to it 62 | } 63 | else 64 | { 65 | finishLoad(); 66 | } 67 | } 68 | }); 69 | 70 | this.last_updated = ko.observable("never"); 71 | this.last_error = ko.observable(); 72 | 73 | this.serialize = function() 74 | { 75 | return { 76 | name : self.name(), 77 | type : self.type(), 78 | settings: self.settings() 79 | }; 80 | } 81 | 82 | this.deserialize = function(object) 83 | { 84 | self.settings(object.settings); 85 | self.name(object.name); 86 | self.type(object.type); 87 | } 88 | 89 | this.getDataRepresentation = function(dataPath) 90 | { 91 | var valueFunction = new Function("data", "return " + dataPath + ";"); 92 | return valueFunction.call(undefined, self.latestData()); 93 | } 94 | 95 | this.updateNow = function() 96 | { 97 | if(!_.isUndefined(self.datasourceInstance) && _.isFunction(self.datasourceInstance.updateNow)) 98 | { 99 | self.datasourceInstance.updateNow(); 100 | } 101 | } 102 | 103 | this.dispose = function() 104 | { 105 | disposeDatasourceInstance(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/js/freeboard/DeveloperConsole.js: -------------------------------------------------------------------------------- 1 | DeveloperConsole = function(theFreeboardModel) 2 | { 3 | function showDeveloperConsole() 4 | { 5 | var pluginScriptsInputs = []; 6 | var container = $('
'); 7 | var addScript = $('
ADD
'); 8 | var table = $('
'); 9 | 10 | table.append($('Plugin Script URL')); 11 | 12 | var tableBody = $(""); 13 | 14 | table.append(tableBody); 15 | 16 | container.append($("

Here you can add references to other scripts to load datasource or widget plugins.

")) 17 | .append(table) 18 | .append(addScript) 19 | .append('

To learn how to build plugins for freeboard, please visit http://freeboard.github.io/freeboard/docs/plugin_example.html

'); 20 | 21 | function refreshScript(scriptURL) 22 | { 23 | $('script[src="' + scriptURL + '"]').remove(); 24 | } 25 | 26 | function addNewScriptRow(scriptURL) 27 | { 28 | var tableRow = $(''); 29 | var tableOperations = $('
    '); 30 | var scriptInput = $(''); 31 | var deleteOperation = $('
  • ').click(function(e){ 32 | pluginScriptsInputs = _.without(pluginScriptsInputs, scriptInput); 33 | tableRow.remove(); 34 | }); 35 | 36 | pluginScriptsInputs.push(scriptInput); 37 | 38 | if(scriptURL) 39 | { 40 | scriptInput.val(scriptURL); 41 | } 42 | 43 | tableOperations.append(deleteOperation); 44 | tableBody 45 | .append(tableRow 46 | .append($('').append(scriptInput)) 47 | .append($('').append(tableOperations))); 48 | } 49 | 50 | _.each(theFreeboardModel.plugins(), function(pluginSource){ 51 | 52 | addNewScriptRow(pluginSource); 53 | 54 | }); 55 | 56 | addScript.click(function(e) 57 | { 58 | addNewScriptRow(); 59 | }); 60 | 61 | new DialogBox(container, "Developer Console", "OK", null, function(){ 62 | 63 | // Unload our previous scripts 64 | _.each(theFreeboardModel.plugins(), function(pluginSource){ 65 | 66 | $('script[src^="' + pluginSource + '"]').remove(); 67 | 68 | }); 69 | 70 | theFreeboardModel.plugins.removeAll(); 71 | 72 | _.each(pluginScriptsInputs, function(scriptInput){ 73 | 74 | var scriptURL = scriptInput.val(); 75 | 76 | if(scriptURL && scriptURL.length > 0) 77 | { 78 | theFreeboardModel.addPluginSource(scriptURL); 79 | 80 | // Load the script with a cache buster 81 | head.js(scriptURL + "?" + Date.now()); 82 | } 83 | }); 84 | 85 | }); 86 | } 87 | 88 | // Public API 89 | return { 90 | showDeveloperConsole : function() 91 | { 92 | showDeveloperConsole(); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/js/freeboard/DialogBox.js: -------------------------------------------------------------------------------- 1 | function DialogBox(contentElement, title, okTitle, cancelTitle, okCallback) 2 | { 3 | var modal_width = 900; 4 | 5 | // Initialize our modal overlay 6 | var overlay = $(''); 7 | 8 | var modalDialog = $(''); 9 | 10 | function closeModal() 11 | { 12 | overlay.fadeOut(200, function() 13 | { 14 | $(this).remove(); 15 | }); 16 | } 17 | 18 | // Create our header 19 | modalDialog.append('

    ' + title + "

    "); 20 | 21 | $('
    ').appendTo(modalDialog).append(contentElement); 22 | 23 | // Create our footer 24 | var footer = $('
    ').appendTo(modalDialog); 25 | 26 | if(okTitle) 27 | { 28 | $('' + okTitle + '').appendTo(footer).click(function() 29 | { 30 | var hold = false; 31 | 32 | if(_.isFunction(okCallback)) 33 | { 34 | hold = okCallback(); 35 | } 36 | 37 | if(!hold) 38 | { 39 | closeModal(); 40 | } 41 | }); 42 | } 43 | 44 | if(cancelTitle) 45 | { 46 | $('' + cancelTitle + '').appendTo(footer).click(function() 47 | { 48 | closeModal(); 49 | }); 50 | } 51 | 52 | overlay.append(modalDialog); 53 | $("body").append(overlay); 54 | overlay.fadeIn(200); 55 | } 56 | -------------------------------------------------------------------------------- /lib/js/freeboard/FreeboardModel.js: -------------------------------------------------------------------------------- 1 | function FreeboardModel(datasourcePlugins, widgetPlugins, freeboardUI) 2 | { 3 | var self = this; 4 | 5 | var SERIALIZATION_VERSION = 1; 6 | 7 | this.version = 0; 8 | this.isEditing = ko.observable(false); 9 | this.allow_edit = ko.observable(false); 10 | this.allow_edit.subscribe(function(newValue) 11 | { 12 | if(newValue) 13 | { 14 | $("#main-header").show(); 15 | } 16 | else 17 | { 18 | $("#main-header").hide(); 19 | } 20 | }); 21 | 22 | this.header_image = ko.observable(); 23 | this.plugins = ko.observableArray(); 24 | this.datasources = ko.observableArray(); 25 | this.panes = ko.observableArray(); 26 | this.datasourceData = {}; 27 | this.processDatasourceUpdate = function(datasourceModel, newData) 28 | { 29 | var datasourceName = datasourceModel.name(); 30 | 31 | self.datasourceData[datasourceName] = newData; 32 | 33 | _.each(self.panes(), function(pane) 34 | { 35 | _.each(pane.widgets(), function(widget) 36 | { 37 | widget.processDatasourceUpdate(datasourceName); 38 | }); 39 | }); 40 | } 41 | 42 | this._datasourceTypes = ko.observable(); 43 | this.datasourceTypes = ko.computed({ 44 | read: function() 45 | { 46 | self._datasourceTypes(); 47 | 48 | var returnTypes = []; 49 | 50 | _.each(datasourcePlugins, function(datasourcePluginType) 51 | { 52 | var typeName = datasourcePluginType.type_name; 53 | var displayName = typeName; 54 | 55 | if(!_.isUndefined(datasourcePluginType.display_name)) 56 | { 57 | displayName = datasourcePluginType.display_name; 58 | } 59 | 60 | returnTypes.push({ 61 | name : typeName, 62 | display_name: displayName 63 | }); 64 | }); 65 | 66 | return returnTypes; 67 | } 68 | }); 69 | 70 | this._widgetTypes = ko.observable(); 71 | this.widgetTypes = ko.computed({ 72 | read: function() 73 | { 74 | self._widgetTypes(); 75 | 76 | var returnTypes = []; 77 | 78 | _.each(widgetPlugins, function(widgetPluginType) 79 | { 80 | var typeName = widgetPluginType.type_name; 81 | var displayName = typeName; 82 | 83 | if(!_.isUndefined(widgetPluginType.display_name)) 84 | { 85 | displayName = widgetPluginType.display_name; 86 | } 87 | 88 | returnTypes.push({ 89 | name : typeName, 90 | display_name: displayName 91 | }); 92 | }); 93 | 94 | return returnTypes; 95 | } 96 | }); 97 | 98 | this.addPluginSource = function(pluginSource) 99 | { 100 | if(pluginSource && self.plugins.indexOf(pluginSource) == -1) 101 | { 102 | self.plugins.push(pluginSource); 103 | } 104 | } 105 | 106 | this.serialize = function() 107 | { 108 | var panes = []; 109 | 110 | _.each(self.panes(), function(pane) 111 | { 112 | panes.push(pane.serialize()); 113 | }); 114 | 115 | var datasources = []; 116 | 117 | _.each(self.datasources(), function(datasource) 118 | { 119 | datasources.push(datasource.serialize()); 120 | }); 121 | 122 | return { 123 | version : SERIALIZATION_VERSION, 124 | header_image: self.header_image(), 125 | allow_edit : self.allow_edit(), 126 | plugins : self.plugins(), 127 | panes : panes, 128 | datasources : datasources, 129 | columns : freeboardUI.getUserColumns() 130 | }; 131 | } 132 | 133 | this.deserialize = function(object, finishedCallback) 134 | { 135 | self.clearDashboard(); 136 | 137 | function finishLoad() 138 | { 139 | freeboardUI.setUserColumns(object.columns); 140 | 141 | if(!_.isUndefined(object.allow_edit)) 142 | { 143 | self.allow_edit(object.allow_edit); 144 | } 145 | else 146 | { 147 | self.allow_edit(true); 148 | } 149 | self.version = object.version || 0; 150 | self.header_image(object.header_image); 151 | 152 | _.each(object.datasources, function(datasourceConfig) 153 | { 154 | var datasource = new DatasourceModel(self, datasourcePlugins); 155 | datasource.deserialize(datasourceConfig); 156 | self.addDatasource(datasource); 157 | }); 158 | 159 | var sortedPanes = _.sortBy(object.panes, function(pane){ 160 | return freeboardUI.getPositionForScreenSize(pane).row; 161 | }); 162 | 163 | _.each(sortedPanes, function(paneConfig) 164 | { 165 | var pane = new PaneModel(self, widgetPlugins); 166 | pane.deserialize(paneConfig); 167 | self.panes.push(pane); 168 | }); 169 | 170 | if(self.allow_edit() && self.panes().length == 0) 171 | { 172 | self.setEditing(true); 173 | } 174 | 175 | if(_.isFunction(finishedCallback)) 176 | { 177 | finishedCallback(); 178 | } 179 | 180 | freeboardUI.processResize(true); 181 | } 182 | 183 | // This could have been self.plugins(object.plugins), but for some weird reason head.js was causing a function to be added to the list of plugins. 184 | _.each(object.plugins, function(plugin) 185 | { 186 | self.addPluginSource(plugin); 187 | }); 188 | 189 | // Load any plugins referenced in this definition 190 | if(_.isArray(object.plugins) && object.plugins.length > 0) 191 | { 192 | head.js(object.plugins, function() 193 | { 194 | finishLoad(); 195 | }); 196 | } 197 | else 198 | { 199 | finishLoad(); 200 | } 201 | } 202 | 203 | this.clearDashboard = function() 204 | { 205 | freeboardUI.removeAllPanes(); 206 | 207 | _.each(self.datasources(), function(datasource) 208 | { 209 | datasource.dispose(); 210 | }); 211 | 212 | _.each(self.panes(), function(pane) 213 | { 214 | pane.dispose(); 215 | }); 216 | 217 | self.plugins.removeAll(); 218 | self.datasources.removeAll(); 219 | self.panes.removeAll(); 220 | } 221 | 222 | this.loadDashboard = function(dashboardData, callback) 223 | { 224 | freeboardUI.showLoadingIndicator(true); 225 | self.deserialize(dashboardData, function() 226 | { 227 | freeboardUI.showLoadingIndicator(false); 228 | 229 | if(_.isFunction(callback)) 230 | { 231 | callback(); 232 | } 233 | 234 | freeboard.emit("dashboard_loaded"); 235 | }); 236 | } 237 | 238 | this.loadDashboardFromLocalFile = function() 239 | { 240 | // Check for the various File API support. 241 | if(window.File && window.FileReader && window.FileList && window.Blob) 242 | { 243 | var input = document.createElement('input'); 244 | input.type = "file"; 245 | $(input).on("change", function(event) 246 | { 247 | var files = event.target.files; 248 | 249 | if(files && files.length > 0) 250 | { 251 | var file = files[0]; 252 | var reader = new FileReader(); 253 | 254 | reader.addEventListener("load", function(fileReaderEvent) 255 | { 256 | 257 | var textFile = fileReaderEvent.target; 258 | var jsonObject = JSON.parse(textFile.result); 259 | 260 | 261 | self.loadDashboard(jsonObject); 262 | self.setEditing(false); 263 | }); 264 | 265 | reader.readAsText(file); 266 | } 267 | 268 | }); 269 | $(input).trigger("click"); 270 | } 271 | else 272 | { 273 | alert('Unable to load a file in this browser.'); 274 | } 275 | } 276 | 277 | this.saveDashboardClicked = function(){ 278 | var target = $(event.currentTarget); 279 | var siblingsShown = target.data('siblings-shown') || false; 280 | if(!siblingsShown){ 281 | $(event.currentTarget).siblings('label').fadeIn('slow'); 282 | }else{ 283 | $(event.currentTarget).siblings('label').fadeOut('slow'); 284 | } 285 | target.data('siblings-shown', !siblingsShown); 286 | } 287 | 288 | this.saveDashboard = function(_thisref, event) 289 | { 290 | var pretty = $(event.currentTarget).data('pretty'); 291 | var contentType = 'application/octet-stream'; 292 | var a = document.createElement('a'); 293 | if(pretty){ 294 | var blob = new Blob([JSON.stringify(self.serialize(), null, '\t')], {'type': contentType}); 295 | }else{ 296 | var blob = new Blob([JSON.stringify(self.serialize())], {'type': contentType}); 297 | } 298 | document.body.appendChild(a); 299 | a.href = window.URL.createObjectURL(blob); 300 | a.download = "dashboard.json"; 301 | a.target="_self"; 302 | a.click(); 303 | } 304 | 305 | this.addDatasource = function(datasource) 306 | { 307 | self.datasources.push(datasource); 308 | } 309 | 310 | this.deleteDatasource = function(datasource) 311 | { 312 | delete self.datasourceData[datasource.name()]; 313 | datasource.dispose(); 314 | self.datasources.remove(datasource); 315 | } 316 | 317 | this.createPane = function() 318 | { 319 | var newPane = new PaneModel(self, widgetPlugins); 320 | self.addPane(newPane); 321 | } 322 | 323 | this.addGridColumnLeft = function() 324 | { 325 | freeboardUI.addGridColumnLeft(); 326 | } 327 | 328 | this.addGridColumnRight = function() 329 | { 330 | freeboardUI.addGridColumnRight(); 331 | } 332 | 333 | this.subGridColumnLeft = function() 334 | { 335 | freeboardUI.subGridColumnLeft(); 336 | } 337 | 338 | this.subGridColumnRight = function() 339 | { 340 | freeboardUI.subGridColumnRight(); 341 | } 342 | 343 | this.addPane = function(pane) 344 | { 345 | self.panes.push(pane); 346 | } 347 | 348 | this.deletePane = function(pane) 349 | { 350 | pane.dispose(); 351 | self.panes.remove(pane); 352 | } 353 | 354 | this.deleteWidget = function(widget) 355 | { 356 | ko.utils.arrayForEach(self.panes(), function(pane) 357 | { 358 | pane.widgets.remove(widget); 359 | }); 360 | 361 | widget.dispose(); 362 | } 363 | 364 | this.setEditing = function(editing, animate) 365 | { 366 | // Don't allow editing if it's not allowed 367 | if(!self.allow_edit() && editing) 368 | { 369 | return; 370 | } 371 | 372 | self.isEditing(editing); 373 | 374 | if(_.isUndefined(animate)) 375 | { 376 | animate = true; 377 | } 378 | 379 | var animateLength = (animate) ? 250 : 0; 380 | var barHeight = $("#admin-bar").outerHeight(); 381 | 382 | if(!editing) 383 | { 384 | $("#toggle-header-icon").addClass("icon-wrench").removeClass("icon-chevron-up"); 385 | $(".gridster .gs_w").css({cursor: "default"}); 386 | $("#main-header").animate({"top": "-" + barHeight + "px"}, animateLength); 387 | $("#board-content").animate({"top": "20"}, animateLength); 388 | $("#main-header").data().shown = false; 389 | $(".sub-section").unbind(); 390 | freeboardUI.disableGrid(); 391 | } 392 | else 393 | { 394 | $("#toggle-header-icon").addClass("icon-chevron-up").removeClass("icon-wrench"); 395 | $(".gridster .gs_w").css({cursor: "pointer"}); 396 | $("#main-header").animate({"top": "0px"}, animateLength); 397 | $("#board-content").animate({"top": (barHeight + 20) + "px"}, animateLength); 398 | $("#main-header").data().shown = true; 399 | freeboardUI.attachWidgetEditIcons($(".sub-section")); 400 | freeboardUI.enableGrid(); 401 | } 402 | 403 | freeboardUI.showPaneEditIcons(editing, animate); 404 | } 405 | 406 | this.toggleEditing = function() 407 | { 408 | var editing = !self.isEditing(); 409 | self.setEditing(editing); 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /lib/js/freeboard/FreeboardUI.js: -------------------------------------------------------------------------------- 1 | function FreeboardUI() 2 | { 3 | var PANE_MARGIN = 10; 4 | var PANE_WIDTH = 300; 5 | var MIN_COLUMNS = 3; 6 | var COLUMN_WIDTH = PANE_MARGIN + PANE_WIDTH + PANE_MARGIN; 7 | 8 | var userColumns = MIN_COLUMNS; 9 | 10 | var loadingIndicator = $('
    '); 11 | var grid; 12 | 13 | function processResize(layoutWidgets) 14 | { 15 | var maxDisplayableColumns = getMaxDisplayableColumnCount(); 16 | var repositionFunction = function(){}; 17 | if(layoutWidgets) 18 | { 19 | repositionFunction = function(index) 20 | { 21 | var paneElement = this; 22 | var paneModel = ko.dataFor(paneElement); 23 | 24 | var newPosition = getPositionForScreenSize(paneModel); 25 | $(paneElement).attr("data-sizex", Math.min(paneModel.col_width(), 26 | maxDisplayableColumns, grid.cols)) 27 | .attr("data-row", newPosition.row) 28 | .attr("data-col", newPosition.col); 29 | 30 | paneModel.processSizeChange(); 31 | } 32 | } 33 | 34 | updateGridWidth(Math.min(maxDisplayableColumns, userColumns)); 35 | 36 | repositionGrid(repositionFunction); 37 | updateGridColumnControls(); 38 | } 39 | 40 | function addGridColumn(shift) 41 | { 42 | var num_cols = grid.cols + 1; 43 | if(updateGridWidth(num_cols)) 44 | { 45 | repositionGrid(function() { 46 | var paneElement = this; 47 | var paneModel = ko.dataFor(paneElement); 48 | 49 | var prevColumnIndex = grid.cols > 1 ? grid.cols - 1 : 1; 50 | var prevCol = paneModel.col[prevColumnIndex]; 51 | var prevRow = paneModel.row[prevColumnIndex]; 52 | var newPosition; 53 | if(shift) 54 | { 55 | leftPreviewCol = true; 56 | var newCol = prevCol < grid.cols ? prevCol + 1 : grid.cols; 57 | newPosition = {row: prevRow, col: newCol}; 58 | } 59 | else 60 | { 61 | rightPreviewCol = true; 62 | newPosition = {row: prevRow, col: prevCol}; 63 | } 64 | $(paneElement).attr("data-sizex", Math.min(paneModel.col_width(), grid.cols)) 65 | .attr("data-row", newPosition.row) 66 | .attr("data-col", newPosition.col); 67 | }); 68 | } 69 | updateGridColumnControls(); 70 | userColumns = grid.cols; 71 | } 72 | 73 | function subtractGridColumn(shift) 74 | { 75 | var num_cols = grid.cols - 1; 76 | if(updateGridWidth(num_cols)) 77 | { 78 | repositionGrid(function() { 79 | var paneElement = this; 80 | var paneModel = ko.dataFor(paneElement); 81 | 82 | var prevColumnIndex = grid.cols + 1; 83 | var prevCol = paneModel.col[prevColumnIndex]; 84 | var prevRow = paneModel.row[prevColumnIndex]; 85 | var newPosition; 86 | if(shift) 87 | { 88 | var newCol = prevCol > 1 ? prevCol - 1 : 1; 89 | newPosition = {row: prevRow, col: newCol}; 90 | } 91 | else 92 | { 93 | var newCol = prevCol <= grid.cols ? prevCol : grid.cols; 94 | newPosition = {row: prevRow, col: newCol}; 95 | } 96 | $(paneElement).attr("data-sizex", Math.min(paneModel.col_width(), grid.cols)) 97 | .attr("data-row", newPosition.row) 98 | .attr("data-col", newPosition.col); 99 | }); 100 | } 101 | updateGridColumnControls(); 102 | userColumns = grid.cols; 103 | } 104 | 105 | function updateGridColumnControls() 106 | { 107 | var col_controls = $(".column-tool"); 108 | var available_width = $("#board-content").width(); 109 | var max_columns = Math.floor(available_width / COLUMN_WIDTH); 110 | 111 | if(grid.cols <= MIN_COLUMNS) 112 | { 113 | col_controls.addClass("min"); 114 | } 115 | else 116 | { 117 | col_controls.removeClass("min"); 118 | } 119 | 120 | if(grid.cols >= max_columns) 121 | { 122 | col_controls.addClass("max"); 123 | } 124 | else 125 | { 126 | col_controls.removeClass("max"); 127 | } 128 | } 129 | 130 | function getMaxDisplayableColumnCount() 131 | { 132 | var available_width = $("#board-content").width(); 133 | return Math.floor(available_width / COLUMN_WIDTH); 134 | } 135 | 136 | function updateGridWidth(newCols) 137 | { 138 | if(newCols === undefined || newCols < MIN_COLUMNS) 139 | { 140 | newCols = MIN_COLUMNS; 141 | } 142 | 143 | var max_columns = getMaxDisplayableColumnCount(); 144 | if(newCols > max_columns) 145 | { 146 | newCols = max_columns; 147 | } 148 | 149 | // +newCols to account for scaling on zoomed browsers 150 | var new_width = (COLUMN_WIDTH * newCols) + newCols; 151 | $(".responsive-column-width").css("max-width", new_width); 152 | 153 | if(newCols === grid.cols) 154 | { 155 | return false; 156 | } 157 | else 158 | { 159 | return true; 160 | } 161 | } 162 | 163 | function repositionGrid(repositionFunction) 164 | { 165 | var rootElement = grid.$el; 166 | 167 | rootElement.find("> li").unbind().removeData(); 168 | $(".responsive-column-width").css("width", ""); 169 | grid.generate_grid_and_stylesheet(); 170 | 171 | rootElement.find("> li").each(repositionFunction); 172 | 173 | grid.init(); 174 | $(".responsive-column-width").css("width", grid.cols * PANE_WIDTH + (grid.cols * PANE_MARGIN * 2)); 175 | } 176 | 177 | function getUserColumns() 178 | { 179 | return userColumns; 180 | } 181 | 182 | function setUserColumns(numCols) 183 | { 184 | userColumns = Math.max(MIN_COLUMNS, numCols); 185 | } 186 | 187 | ko.bindingHandlers.grid = { 188 | init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) 189 | { 190 | // Initialize our grid 191 | grid = $(element).gridster({ 192 | widget_margins : [PANE_MARGIN, PANE_MARGIN], 193 | widget_base_dimensions: [PANE_WIDTH, 10], 194 | resize: { 195 | enabled : false, 196 | axes : "x" 197 | } 198 | }).data("gridster"); 199 | 200 | processResize(false) 201 | 202 | grid.disable(); 203 | } 204 | } 205 | 206 | function addPane(element, viewModel, isEditing) 207 | { 208 | var position = getPositionForScreenSize(viewModel); 209 | var col = position.col; 210 | var row = position.row; 211 | var width = Number(viewModel.width()); 212 | var height = Number(viewModel.getCalculatedHeight()); 213 | 214 | grid.add_widget(element, width, height, col, row); 215 | 216 | if(isEditing) 217 | { 218 | showPaneEditIcons(true); 219 | } 220 | 221 | updatePositionForScreenSize(viewModel, row, col); 222 | 223 | $(element).attrchange({ 224 | trackValues: true, 225 | callback : function(event) 226 | { 227 | if(event.attributeName == "data-row") 228 | { 229 | updatePositionForScreenSize(viewModel, Number(event.newValue), undefined); 230 | } 231 | else if(event.attributeName == "data-col") 232 | { 233 | updatePositionForScreenSize(viewModel, undefined, Number(event.newValue)); 234 | } 235 | } 236 | }); 237 | } 238 | 239 | function updatePane(element, viewModel) 240 | { 241 | // If widget has been added or removed 242 | var calculatedHeight = viewModel.getCalculatedHeight(); 243 | 244 | var elementHeight = Number($(element).attr("data-sizey")); 245 | var elementWidth = Number($(element).attr("data-sizex")); 246 | 247 | if(calculatedHeight != elementHeight || viewModel.col_width() != elementWidth) 248 | { 249 | grid.resize_widget($(element), viewModel.col_width(), calculatedHeight, function(){ 250 | grid.set_dom_grid_height(); 251 | }); 252 | } 253 | } 254 | 255 | function updatePositionForScreenSize(paneModel, row, col) 256 | { 257 | var displayCols = grid.cols; 258 | 259 | if(!_.isUndefined(row)) paneModel.row[displayCols] = row; 260 | if(!_.isUndefined(col)) paneModel.col[displayCols] = col; 261 | } 262 | 263 | function showLoadingIndicator(show) 264 | { 265 | if(show) 266 | { 267 | loadingIndicator.fadeOut(0).appendTo("body").fadeIn(500); 268 | } 269 | else 270 | { 271 | loadingIndicator.fadeOut(500).remove(); 272 | } 273 | } 274 | 275 | function showPaneEditIcons(show, animate) 276 | { 277 | if(_.isUndefined(animate)) 278 | { 279 | animate = true; 280 | } 281 | 282 | var animateLength = (animate) ? 250 : 0; 283 | 284 | if(show) 285 | { 286 | $(".pane-tools").fadeIn(animateLength);//.css("display", "block").animate({opacity: 1.0}, animateLength); 287 | $("#column-tools").fadeIn(animateLength); 288 | } 289 | else 290 | { 291 | $(".pane-tools").fadeOut(animateLength);//.animate({opacity: 0.0}, animateLength).css("display", "none");//, function() 292 | $("#column-tools").fadeOut(animateLength); 293 | } 294 | } 295 | 296 | function attachWidgetEditIcons(element) 297 | { 298 | $(element).hover(function() 299 | { 300 | showWidgetEditIcons(this, true); 301 | }, function() 302 | { 303 | showWidgetEditIcons(this, false); 304 | }); 305 | } 306 | 307 | function showWidgetEditIcons(element, show) 308 | { 309 | if(show) 310 | { 311 | $(element).find(".sub-section-tools").fadeIn(250); 312 | } 313 | else 314 | { 315 | $(element).find(".sub-section-tools").fadeOut(250); 316 | } 317 | } 318 | 319 | function getPositionForScreenSize(paneModel) 320 | { 321 | var cols = grid.cols; 322 | 323 | if(_.isNumber(paneModel.row) && _.isNumber(paneModel.col)) // Support for legacy format 324 | { 325 | var obj = {}; 326 | obj[cols] = paneModel.row; 327 | paneModel.row = obj; 328 | 329 | 330 | obj = {}; 331 | obj[cols] = paneModel.col; 332 | paneModel.col = obj; 333 | } 334 | 335 | var newColumnIndex = 1; 336 | var columnDiff = 1000; 337 | 338 | for(var columnIndex in paneModel.col) 339 | { 340 | if(columnIndex == cols) // If we already have a position defined for this number of columns, return that position 341 | { 342 | return {row: paneModel.row[columnIndex], col: paneModel.col[columnIndex]}; 343 | } 344 | else if(paneModel.col[columnIndex] > cols) // If it's greater than our display columns, put it in the last column 345 | { 346 | newColumnIndex = cols; 347 | } 348 | else // If it's less than, pick whichever one is closest 349 | { 350 | var delta = cols - columnIndex; 351 | 352 | if(delta < columnDiff) 353 | { 354 | newColumnIndex = columnIndex; 355 | columnDiff = delta; 356 | } 357 | } 358 | } 359 | 360 | if(newColumnIndex in paneModel.col && newColumnIndex in paneModel.row) 361 | { 362 | return {row: paneModel.row[newColumnIndex], col: paneModel.col[newColumnIndex]}; 363 | } 364 | 365 | return {row:1,col:newColumnIndex}; 366 | } 367 | 368 | 369 | // Public Functions 370 | return { 371 | showLoadingIndicator : function(show) 372 | { 373 | showLoadingIndicator(show); 374 | }, 375 | showPaneEditIcons : function(show, animate) 376 | { 377 | showPaneEditIcons(show, animate); 378 | }, 379 | attachWidgetEditIcons : function(element) 380 | { 381 | attachWidgetEditIcons(element); 382 | }, 383 | getPositionForScreenSize : function(paneModel) 384 | { 385 | return getPositionForScreenSize(paneModel); 386 | }, 387 | processResize : function(layoutWidgets) 388 | { 389 | processResize(layoutWidgets); 390 | }, 391 | disableGrid : function() 392 | { 393 | grid.disable(); 394 | }, 395 | enableGrid : function() 396 | { 397 | grid.enable(); 398 | }, 399 | addPane : function(element, viewModel, isEditing) 400 | { 401 | addPane(element, viewModel, isEditing); 402 | }, 403 | updatePane : function(element, viewModel) 404 | { 405 | updatePane(element, viewModel); 406 | }, 407 | removePane : function(element) 408 | { 409 | grid.remove_widget(element); 410 | }, 411 | removeAllPanes : function() 412 | { 413 | grid.remove_all_widgets(); 414 | }, 415 | addGridColumnLeft : function() 416 | { 417 | addGridColumn(true); 418 | }, 419 | addGridColumnRight : function() 420 | { 421 | addGridColumn(false); 422 | }, 423 | subGridColumnLeft : function() 424 | { 425 | subtractGridColumn(true); 426 | }, 427 | subGridColumnRight : function() 428 | { 429 | subtractGridColumn(false); 430 | }, 431 | getUserColumns : function() 432 | { 433 | return getUserColumns(); 434 | }, 435 | setUserColumns : function(numCols) 436 | { 437 | setUserColumns(numCols); 438 | } 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /lib/js/freeboard/JSEditor.js: -------------------------------------------------------------------------------- 1 | JSEditor = function () { 2 | var assetRoot = "" 3 | 4 | function setAssetRoot(_assetRoot) { 5 | assetRoot = _assetRoot; 6 | } 7 | 8 | function displayJSEditor(value, callback) { 9 | 10 | var exampleText = "// Example: Convert temp from C to F and truncate to 2 decimal places.\n// return (datasources[\"MyDatasource\"].sensor.tempInF * 1.8 + 32).toFixed(2);"; 11 | 12 | // If value is empty, go ahead and suggest something 13 | if (!value) { 14 | value = exampleText; 15 | } 16 | 17 | var codeWindow = $('
    '); 18 | var codeMirrorWrapper = $('
    '); 19 | var codeWindowFooter = $(''); 20 | var codeWindowHeader = $('
    This javascript will be re-evaluated any time a datasource referenced here is updated, and the value you return will be displayed in the widget. You can assume this javascript is wrapped in a function of the form function(datasources) where datasources is a collection of javascript objects (keyed by their name) corresponding to the most current data in a datasource.
    '); 21 | 22 | codeWindow.append([codeWindowHeader, codeMirrorWrapper, codeWindowFooter]); 23 | 24 | $("body").append(codeWindow); 25 | 26 | var codeMirrorEditor = CodeMirror(codeMirrorWrapper.get(0), 27 | { 28 | value: value, 29 | mode: "javascript", 30 | theme: "ambiance", 31 | indentUnit: 4, 32 | lineNumbers: true, 33 | matchBrackets: true, 34 | autoCloseBrackets: true 35 | } 36 | ); 37 | 38 | var closeButton = $('Close').click(function () { 39 | if (callback) { 40 | var newValue = codeMirrorEditor.getValue(); 41 | 42 | if (newValue === exampleText) { 43 | newValue = ""; 44 | } 45 | 46 | callback(newValue); 47 | codeWindow.remove(); 48 | } 49 | }); 50 | 51 | codeWindowFooter.append(closeButton); 52 | } 53 | 54 | // Public API 55 | return { 56 | displayJSEditor: function (value, callback) { 57 | displayJSEditor(value, callback); 58 | }, 59 | setAssetRoot: function (assetRoot) { 60 | setAssetRoot(assetRoot) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/js/freeboard/PaneModel.js: -------------------------------------------------------------------------------- 1 | function PaneModel(theFreeboardModel, widgetPlugins) { 2 | var self = this; 3 | 4 | this.title = ko.observable(); 5 | this.width = ko.observable(1); 6 | this.row = {}; 7 | this.col = {}; 8 | 9 | this.col_width = ko.observable(1); 10 | this.col_width.subscribe(function(newValue) 11 | { 12 | self.processSizeChange(); 13 | }); 14 | 15 | this.widgets = ko.observableArray(); 16 | 17 | this.addWidget = function (widget) { 18 | this.widgets.push(widget); 19 | } 20 | 21 | this.widgetCanMoveUp = function (widget) { 22 | return (self.widgets.indexOf(widget) >= 1); 23 | } 24 | 25 | this.widgetCanMoveDown = function (widget) { 26 | var i = self.widgets.indexOf(widget); 27 | 28 | return (i < self.widgets().length - 1); 29 | } 30 | 31 | this.moveWidgetUp = function (widget) { 32 | if (self.widgetCanMoveUp(widget)) { 33 | var i = self.widgets.indexOf(widget); 34 | var array = self.widgets(); 35 | self.widgets.splice(i - 1, 2, array[i], array[i - 1]); 36 | } 37 | } 38 | 39 | this.moveWidgetDown = function (widget) { 40 | if (self.widgetCanMoveDown(widget)) { 41 | var i = self.widgets.indexOf(widget); 42 | var array = self.widgets(); 43 | self.widgets.splice(i, 2, array[i + 1], array[i]); 44 | } 45 | } 46 | 47 | this.processSizeChange = function() 48 | { 49 | // Give the animation a moment to complete. Really hacky. 50 | // TODO: Make less hacky. Also, doesn't work when screen resizes. 51 | setTimeout(function(){ 52 | _.each(self.widgets(), function (widget) { 53 | widget.processSizeChange(); 54 | }); 55 | }, 1000); 56 | } 57 | 58 | this.getCalculatedHeight = function () { 59 | var sumHeights = _.reduce(self.widgets(), function (memo, widget) { 60 | return memo + widget.height(); 61 | }, 0); 62 | 63 | sumHeights *= 6; 64 | sumHeights += 3; 65 | 66 | sumHeights *= 10; 67 | 68 | var rows = Math.ceil((sumHeights + 20) / 30); 69 | 70 | return Math.max(4, rows); 71 | } 72 | 73 | this.serialize = function () { 74 | var widgets = []; 75 | 76 | _.each(self.widgets(), function (widget) { 77 | widgets.push(widget.serialize()); 78 | }); 79 | 80 | return { 81 | title: self.title(), 82 | width: self.width(), 83 | row: self.row, 84 | col: self.col, 85 | col_width: self.col_width(), 86 | widgets: widgets 87 | }; 88 | } 89 | 90 | this.deserialize = function (object) { 91 | self.title(object.title); 92 | self.width(object.width); 93 | 94 | self.row = object.row; 95 | self.col = object.col; 96 | self.col_width(object.col_width || 1); 97 | 98 | _.each(object.widgets, function (widgetConfig) { 99 | var widget = new WidgetModel(theFreeboardModel, widgetPlugins); 100 | widget.deserialize(widgetConfig); 101 | self.widgets.push(widget); 102 | }); 103 | } 104 | 105 | this.dispose = function () { 106 | _.each(self.widgets(), function (widget) { 107 | widget.dispose(); 108 | }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/js/freeboard/ValueEditor.js: -------------------------------------------------------------------------------- 1 | ValueEditor = function(theFreeboardModel) 2 | { 3 | var _veDatasourceRegex = new RegExp(".*datasources\\[\"([^\"]*)(\"\\])?(.*)$"); 4 | 5 | var dropdown = null; 6 | var selectedOptionIndex = 0; 7 | var _autocompleteOptions = []; 8 | var currentValue = null; 9 | 10 | var EXPECTED_TYPE = { 11 | ANY : "any", 12 | ARRAY : "array", 13 | OBJECT : "object", 14 | STRING : "string", 15 | NUMBER : "number", 16 | BOOLEAN : "boolean" 17 | }; 18 | 19 | function _isPotentialTypeMatch(value, expectsType) 20 | { 21 | if(_.isArray(value) || _.isObject(value)) 22 | { 23 | return true; 24 | } 25 | return _isTypeMatch(value, expectsType); 26 | } 27 | 28 | function _isTypeMatch(value, expectsType) { 29 | switch(expectsType) 30 | { 31 | case EXPECTED_TYPE.ANY: return true; 32 | case EXPECTED_TYPE.ARRAY: return _.isArray(value); 33 | case EXPECTED_TYPE.OBJECT: return _.isObject(value); 34 | case EXPECTED_TYPE.STRING: return _.isString(value); 35 | case EXPECTED_TYPE.NUMBER: return _.isNumber(value); 36 | case EXPECTED_TYPE.BOOLEAN: return _.isBoolean(value); 37 | } 38 | } 39 | 40 | function _checkCurrentValueType(element, expectsType) { 41 | $(element).parent().find(".validation-error").remove(); 42 | if(!_isTypeMatch(currentValue, expectsType)) { 43 | $(element).parent().append("
    " + 44 | "This field expects an expression that evaluates to type " + 45 | expectsType + ".
    "); 46 | } 47 | } 48 | 49 | function _resizeValueEditor(element) 50 | { 51 | var lineBreakCount = ($(element).val().match(/\n/g) || []).length; 52 | 53 | var newHeight = Math.min(200, 20 * (lineBreakCount + 1)); 54 | 55 | $(element).css({height: newHeight + "px"}); 56 | } 57 | 58 | function _autocompleteFromDatasource(inputString, datasources, expectsType) 59 | { 60 | var match = _veDatasourceRegex.exec(inputString); 61 | 62 | var options = []; 63 | 64 | if(match) 65 | { 66 | // Editor value is: datasources["; List all datasources 67 | if(match[1] == "") 68 | { 69 | _.each(datasources, function(datasource) 70 | { 71 | options.push({value: datasource.name(), entity: undefined, 72 | precede_char: "", follow_char: "\"]"}); 73 | }); 74 | } 75 | // Editor value is a partial match for a datasource; list matching datasources 76 | else if(match[1] != "" && _.isUndefined(match[2])) 77 | { 78 | var replacementString = match[1]; 79 | 80 | _.each(datasources, function(datasource) 81 | { 82 | var dsName = datasource.name(); 83 | 84 | if(dsName != replacementString && dsName.indexOf(replacementString) == 0) 85 | { 86 | options.push({value: dsName, entity: undefined, 87 | precede_char: "", follow_char: "\"]"}); 88 | } 89 | }); 90 | } 91 | // Editor value matches a datasources; parse JSON in order to populate list 92 | else 93 | { 94 | // We already have a datasource selected; find it 95 | var datasource = _.find(datasources, function(datasource) 96 | { 97 | return (datasource.name() === match[1]); 98 | }); 99 | 100 | if(!_.isUndefined(datasource)) 101 | { 102 | var dataPath = "data"; 103 | var remainder = ""; 104 | 105 | // Parse the partial JSON selectors 106 | if(!_.isUndefined(match[2])) 107 | { 108 | // Strip any incomplete field values, and store the remainder 109 | var remainderIndex = match[3].lastIndexOf("]") + 1; 110 | dataPath = dataPath + match[3].substring(0, remainderIndex); 111 | remainder = match[3].substring(remainderIndex, match[3].length); 112 | remainder = remainder.replace(/^[\[\"]*/, ""); 113 | remainder = remainder.replace(/[\"\]]*$/, ""); 114 | } 115 | 116 | // Get the data for the last complete JSON field 117 | var dataValue = datasource.getDataRepresentation(dataPath); 118 | currentValue = dataValue; 119 | 120 | // For arrays, list out the indices 121 | if(_.isArray(dataValue)) 122 | { 123 | for(var index = 0; index < dataValue.length; index++) 124 | { 125 | if(index.toString().indexOf(remainder) == 0) 126 | { 127 | var value = dataValue[index]; 128 | if(_isPotentialTypeMatch(value, expectsType)) 129 | { 130 | options.push({value: index, entity: value, 131 | precede_char: "[", follow_char: "]", 132 | preview: value.toString()}); 133 | } 134 | } 135 | } 136 | } 137 | // For objects, list out the keys 138 | else if(_.isObject(dataValue)) 139 | { 140 | _.each(dataValue, function(value, name) 141 | { 142 | if(name.indexOf(remainder) == 0) 143 | { 144 | if(_isPotentialTypeMatch(value, expectsType)) 145 | { 146 | options.push({value: name, entity: value, 147 | precede_char: "[\"", follow_char: "\"]"}); 148 | } 149 | } 150 | }); 151 | } 152 | // For everything else, do nothing (no further selection possible) 153 | else 154 | { 155 | // no-op 156 | } 157 | } 158 | } 159 | } 160 | _autocompleteOptions = options; 161 | } 162 | 163 | function _renderAutocompleteDropdown(element, expectsType) 164 | { 165 | var inputString = $(element).val().substring(0, $(element).getCaretPosition()); 166 | 167 | // Weird issue where the textarea box was putting in ASCII (nbsp) for spaces. 168 | inputString = inputString.replace(String.fromCharCode(160), " "); 169 | 170 | _autocompleteFromDatasource(inputString, theFreeboardModel.datasources(), expectsType); 171 | 172 | if(_autocompleteOptions.length > 0) 173 | { 174 | if(!dropdown) 175 | { 176 | dropdown = $('
      ') 177 | .insertAfter(element) 178 | .width($(element).outerWidth() - 2) 179 | .css("left", $(element).position().left) 180 | .css("top", $(element).position().top + $(element).outerHeight() - 1); 181 | } 182 | 183 | dropdown.empty(); 184 | dropdown.scrollTop(0); 185 | 186 | var selected = true; 187 | selectedOptionIndex = 0; 188 | 189 | _.each(_autocompleteOptions, function(option, index) 190 | { 191 | var li = _renderAutocompleteDropdownOption(element, inputString, option, index); 192 | if(selected) 193 | { 194 | $(li).addClass("selected"); 195 | selected = false; 196 | } 197 | }); 198 | } 199 | else 200 | { 201 | _checkCurrentValueType(element, expectsType); 202 | $(element).next("ul#value-selector").remove(); 203 | dropdown = null; 204 | selectedOptionIndex = -1; 205 | } 206 | } 207 | 208 | function _renderAutocompleteDropdownOption(element, inputString, option, currentIndex) 209 | { 210 | var optionLabel = option.value; 211 | if(option.preview) 212 | { 213 | optionLabel = optionLabel + "" + option.preview + ""; 214 | } 215 | var li = $('
    • ' + optionLabel + '
    • ').appendTo(dropdown) 216 | .mouseenter(function() 217 | { 218 | $(this).trigger("freeboard-select"); 219 | }) 220 | .mousedown(function(event) 221 | { 222 | $(this).trigger("freeboard-insertValue"); 223 | event.preventDefault(); 224 | }) 225 | .data("freeboard-optionIndex", currentIndex) 226 | .data("freeboard-optionValue", option.value) 227 | .bind("freeboard-insertValue", function() 228 | { 229 | var optionValue = option.value; 230 | optionValue = option.precede_char + optionValue + option.follow_char; 231 | 232 | var replacementIndex = inputString.lastIndexOf("]"); 233 | if(replacementIndex != -1) 234 | { 235 | $(element).replaceTextAt(replacementIndex+1, $(element).val().length, 236 | optionValue); 237 | } 238 | else 239 | { 240 | $(element).insertAtCaret(optionValue); 241 | } 242 | 243 | currentValue = option.entity; 244 | $(element).triggerHandler("mouseup"); 245 | }) 246 | .bind("freeboard-select", function() 247 | { 248 | $(this).parent().find("li.selected").removeClass("selected"); 249 | $(this).addClass("selected"); 250 | selectedOptionIndex = $(this).data("freeboard-optionIndex"); 251 | }); 252 | return li; 253 | } 254 | 255 | function createValueEditor(element, expectsType) 256 | { 257 | $(element).addClass("calculated-value-input") 258 | .bind("keyup mouseup freeboard-eval", function(event) { 259 | // Ignore arrow keys and enter keys 260 | if(dropdown && event.type == "keyup" 261 | && (event.keyCode == 38 || event.keyCode == 40 || event.keyCode == 13)) 262 | { 263 | event.preventDefault(); 264 | return; 265 | } 266 | _renderAutocompleteDropdown(element, expectsType); 267 | }) 268 | .focus(function() 269 | { 270 | $(element).css({"z-index" : 3001}); 271 | _resizeValueEditor(element); 272 | }) 273 | .focusout(function() 274 | { 275 | _checkCurrentValueType(element, expectsType); 276 | $(element).css({ 277 | "height": "", 278 | "z-index" : 3000 279 | }); 280 | $(element).next("ul#value-selector").remove(); 281 | dropdown = null; 282 | selectedOptionIndex = -1; 283 | }) 284 | .bind("keydown", function(event) 285 | { 286 | 287 | if(dropdown) 288 | { 289 | if(event.keyCode == 38 || event.keyCode == 40) // Handle Arrow keys 290 | { 291 | event.preventDefault(); 292 | 293 | var optionItems = $(dropdown).find("li"); 294 | 295 | if(event.keyCode == 38) // Up Arrow 296 | { 297 | selectedOptionIndex--; 298 | } 299 | else if(event.keyCode == 40) // Down Arrow 300 | { 301 | selectedOptionIndex++; 302 | } 303 | 304 | if(selectedOptionIndex < 0) 305 | { 306 | selectedOptionIndex = optionItems.size() - 1; 307 | } 308 | else if(selectedOptionIndex >= optionItems.size()) 309 | { 310 | selectedOptionIndex = 0; 311 | } 312 | 313 | var optionElement = $(optionItems).eq(selectedOptionIndex); 314 | 315 | optionElement.trigger("freeboard-select"); 316 | $(dropdown).scrollTop($(optionElement).position().top); 317 | } 318 | else if(event.keyCode == 13) // Handle enter key 319 | { 320 | event.preventDefault(); 321 | 322 | if(selectedOptionIndex != -1) 323 | { 324 | $(dropdown).find("li").eq(selectedOptionIndex) 325 | .trigger("freeboard-insertValue"); 326 | } 327 | } 328 | } 329 | }); 330 | } 331 | 332 | // Public API 333 | return { 334 | createValueEditor : function(element, expectsType) 335 | { 336 | if(expectsType) 337 | { 338 | createValueEditor(element, expectsType); 339 | } 340 | else { 341 | createValueEditor(element, EXPECTED_TYPE.ANY); 342 | } 343 | }, 344 | EXPECTED_TYPE : EXPECTED_TYPE 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /lib/js/freeboard/WidgetModel.js: -------------------------------------------------------------------------------- 1 | function WidgetModel(theFreeboardModel, widgetPlugins) { 2 | function disposeWidgetInstance() { 3 | if (!_.isUndefined(self.widgetInstance)) { 4 | if (_.isFunction(self.widgetInstance.onDispose)) { 5 | self.widgetInstance.onDispose(); 6 | } 7 | 8 | self.widgetInstance = undefined; 9 | } 10 | } 11 | 12 | var self = this; 13 | 14 | this.datasourceRefreshNotifications = {}; 15 | this.calculatedSettingScripts = {}; 16 | 17 | this.title = ko.observable(); 18 | this.fillSize = ko.observable(false); 19 | 20 | this.type = ko.observable(); 21 | this.type.subscribe(function (newValue) { 22 | disposeWidgetInstance(); 23 | 24 | if ((newValue in widgetPlugins) && _.isFunction(widgetPlugins[newValue].newInstance)) { 25 | var widgetType = widgetPlugins[newValue]; 26 | 27 | function finishLoad() { 28 | widgetType.newInstance(self.settings(), function (widgetInstance) { 29 | 30 | self.fillSize((widgetType.fill_size === true)); 31 | self.widgetInstance = widgetInstance; 32 | self.shouldRender(true); 33 | self._heightUpdate.valueHasMutated(); 34 | 35 | }); 36 | } 37 | 38 | // Do we need to load any external scripts? 39 | if (widgetType.external_scripts) { 40 | head.js(widgetType.external_scripts.slice(0), finishLoad); // Need to clone the array because head.js adds some weird functions to it 41 | } 42 | else { 43 | finishLoad(); 44 | } 45 | } 46 | }); 47 | 48 | this.settings = ko.observable({}); 49 | this.settings.subscribe(function (newValue) { 50 | if (!_.isUndefined(self.widgetInstance) && _.isFunction(self.widgetInstance.onSettingsChanged)) { 51 | self.widgetInstance.onSettingsChanged(newValue); 52 | } 53 | 54 | self.updateCalculatedSettings(); 55 | self._heightUpdate.valueHasMutated(); 56 | }); 57 | 58 | this.processDatasourceUpdate = function (datasourceName) { 59 | var refreshSettingNames = self.datasourceRefreshNotifications[datasourceName]; 60 | 61 | if (_.isArray(refreshSettingNames)) { 62 | _.each(refreshSettingNames, function (settingName) { 63 | self.processCalculatedSetting(settingName); 64 | }); 65 | } 66 | } 67 | 68 | this.callValueFunction = function (theFunction) { 69 | return theFunction.call(undefined, theFreeboardModel.datasourceData); 70 | } 71 | 72 | this.processSizeChange = function () { 73 | if (!_.isUndefined(self.widgetInstance) && _.isFunction(self.widgetInstance.onSizeChanged)) { 74 | self.widgetInstance.onSizeChanged(); 75 | } 76 | } 77 | 78 | this.processCalculatedSetting = function (settingName) { 79 | if (_.isFunction(self.calculatedSettingScripts[settingName])) { 80 | var returnValue = undefined; 81 | 82 | try { 83 | returnValue = self.callValueFunction(self.calculatedSettingScripts[settingName]); 84 | } 85 | catch (e) { 86 | var rawValue = self.settings()[settingName]; 87 | 88 | // If there is a reference error and the value just contains letters and numbers, then 89 | if (e instanceof ReferenceError && (/^\w+$/).test(rawValue)) { 90 | returnValue = rawValue; 91 | } 92 | } 93 | 94 | if (!_.isUndefined(self.widgetInstance) && _.isFunction(self.widgetInstance.onCalculatedValueChanged) && !_.isUndefined(returnValue)) { 95 | try { 96 | self.widgetInstance.onCalculatedValueChanged(settingName, returnValue); 97 | } 98 | catch (e) { 99 | console.log(e.toString()); 100 | } 101 | } 102 | } 103 | } 104 | 105 | this.updateCalculatedSettings = function () { 106 | self.datasourceRefreshNotifications = {}; 107 | self.calculatedSettingScripts = {}; 108 | 109 | if (_.isUndefined(self.type())) { 110 | return; 111 | } 112 | 113 | // Check for any calculated settings 114 | var settingsDefs = widgetPlugins[self.type()].settings; 115 | var datasourceRegex = new RegExp("datasources.([\\w_-]+)|datasources\\[['\"]([^'\"]+)", "g"); 116 | var currentSettings = self.settings(); 117 | 118 | _.each(settingsDefs, function (settingDef) { 119 | if (settingDef.type == "calculated") { 120 | var script = currentSettings[settingDef.name]; 121 | 122 | if (!_.isUndefined(script)) { 123 | 124 | if(_.isArray(script)) { 125 | script = "[" + script.join(",") + "]"; 126 | } 127 | 128 | // If there is no return, add one 129 | if ((script.match(/;/g) || []).length <= 1 && script.indexOf("return") == -1) { 130 | script = "return " + script; 131 | } 132 | 133 | var valueFunction; 134 | 135 | try { 136 | valueFunction = new Function("datasources", script); 137 | } 138 | catch (e) { 139 | var literalText = currentSettings[settingDef.name].replace(/"/g, '\\"').replace(/[\r\n]/g, ' \\\n'); 140 | 141 | // If the value function cannot be created, then go ahead and treat it as literal text 142 | valueFunction = new Function("datasources", "return \"" + literalText + "\";"); 143 | } 144 | 145 | self.calculatedSettingScripts[settingDef.name] = valueFunction; 146 | self.processCalculatedSetting(settingDef.name); 147 | 148 | // Are there any datasources we need to be subscribed to? 149 | var matches; 150 | 151 | while (matches = datasourceRegex.exec(script)) { 152 | var dsName = (matches[1] || matches[2]); 153 | var refreshSettingNames = self.datasourceRefreshNotifications[dsName]; 154 | 155 | if (_.isUndefined(refreshSettingNames)) { 156 | refreshSettingNames = []; 157 | self.datasourceRefreshNotifications[dsName] = refreshSettingNames; 158 | } 159 | 160 | if(_.indexOf(refreshSettingNames, settingDef.name) == -1) // Only subscribe to this notification once. 161 | { 162 | refreshSettingNames.push(settingDef.name); 163 | } 164 | } 165 | } 166 | } 167 | }); 168 | } 169 | 170 | this._heightUpdate = ko.observable(); 171 | this.height = ko.computed({ 172 | read: function () { 173 | self._heightUpdate(); 174 | 175 | if (!_.isUndefined(self.widgetInstance) && _.isFunction(self.widgetInstance.getHeight)) { 176 | return self.widgetInstance.getHeight(); 177 | } 178 | 179 | return 1; 180 | } 181 | }); 182 | 183 | this.shouldRender = ko.observable(false); 184 | this.render = function (element) { 185 | self.shouldRender(false); 186 | if (!_.isUndefined(self.widgetInstance) && _.isFunction(self.widgetInstance.render)) { 187 | self.widgetInstance.render(element); 188 | self.updateCalculatedSettings(); 189 | } 190 | } 191 | 192 | this.dispose = function () { 193 | 194 | } 195 | 196 | this.serialize = function () { 197 | return { 198 | title: self.title(), 199 | type: self.type(), 200 | settings: self.settings() 201 | }; 202 | } 203 | 204 | this.deserialize = function (object) { 205 | self.title(object.title); 206 | self.settings(object.settings); 207 | self.type(object.type); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /lib/js/thirdparty/head.js: -------------------------------------------------------------------------------- 1 | /*! head.core - v1.0.2 */ 2 | (function(n,t){"use strict";function r(n){a[a.length]=n}function k(n){var t=new RegExp(" ?\\b"+n+"\\b");c.className=c.className.replace(t,"")}function p(n,t){for(var i=0,r=n.length;in?(i.screensCss.gt&&r("gt-"+n),i.screensCss.gte&&r("gte-"+n)):tt);u.feature("landscape",fe?(i.browserCss.gt&&r("gt-"+f+e),i.browserCss.gte&&r("gte-"+f+e)):h2&&this[u+1]!==t)u&&r(this.slice(u,u+1).join("-").toLowerCase()+i.section);else{var f=n||"index",e=f.indexOf(".");e>0&&(f=f.substring(0,e));c.id=f.toLowerCase()+i.page;u||r("root"+i.section)}});u.screen={height:n.screen.height,width:n.screen.width};tt();b=0;n.addEventListener?n.addEventListener("resize",it,!1):n.attachEvent("onresize",it)})(window); 3 | /*! head.css3 - v1.0.0 */ 4 | (function(n,t){"use strict";function a(n){for(var r in n)if(i[n[r]]!==t)return!0;return!1}function r(n){var t=n.charAt(0).toUpperCase()+n.substr(1),i=(n+" "+c.join(t+" ")+t).split(" ");return!!a(i)}var h=n.document,o=h.createElement("i"),i=o.style,s=" -o- -moz- -ms- -webkit- -khtml- ".split(" "),c="Webkit Moz O ms Khtml".split(" "),l=n.head_conf&&n.head_conf.head||"head",u=n[l],f={gradient:function(){var n="background-image:";return i.cssText=(n+s.join("gradient(linear,left top,right bottom,from(#9f9),to(#fff));"+n)+s.join("linear-gradient(left top,#eee,#fff);"+n)).slice(0,-n.length),!!i.backgroundImage},rgba:function(){return i.cssText="background-color:rgba(0,0,0,0.5)",!!i.backgroundColor},opacity:function(){return o.style.opacity===""},textshadow:function(){return i.textShadow===""},multiplebgs:function(){i.cssText="background:url(https://),url(https://),red url(https://)";var n=(i.background||"").match(/url/g);return Object.prototype.toString.call(n)==="[object Array]"&&n.length===3},boxshadow:function(){return r("boxShadow")},borderimage:function(){return r("borderImage")},borderradius:function(){return r("borderRadius")},cssreflections:function(){return r("boxReflect")},csstransforms:function(){return r("transform")},csstransitions:function(){return r("transition")},touch:function(){return"ontouchstart"in n},retina:function(){return n.devicePixelRatio>1},fontface:function(){var t=u.browser.name,n=u.browser.version;switch(t){case"ie":return n>=9;case"chrome":return n>=13;case"ff":return n>=6;case"ios":return n>=5;case"android":return!1;case"webkit":return n>=5.1;case"opera":return n>=10;default:return!1}}};for(var e in f)f[e]&&u.feature(e,f[e].call(),!0);u.feature()})(window); 5 | /*! head.load - v1.0.3 */ 6 | (function(n,t){"use strict";function w(){}function u(n,t){if(n){typeof n=="object"&&(n=[].slice.call(n));for(var i=0,r=n.length;i