sinatra-liveviews 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3e4050bfda0d859257cca1ae07c88d4dd2a1667e
4
+ data.tar.gz: 16746a5b6f1358d2dad46aa81d1d0c9f75a97abb
5
+ SHA512:
6
+ metadata.gz: 25a70fe0a0103fe46624f23df6a308d2c8b68244ebd4b0640cc8dff3c4bff4a3af123f7b8736852bf5da0ce541884132a281120df85fb1c0f64d87d3409c2b00
7
+ data.tar.gz: 58e6a8e2aa33e03d14b04a7c3b88c31af99c6d78552516af5c91d48cb5bdc1df4308283a9d4066f4bd927d14efeec55ccb568189d336dc55d481eba34ea105de
@@ -0,0 +1 @@
1
+ liveviews.demo.db
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sinatra-liveviews.gemspec
4
+ gemspec
@@ -0,0 +1,17 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sinatra-liveviews (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ rake (10.4.2)
10
+
11
+ PLATFORMS
12
+ ruby
13
+
14
+ DEPENDENCIES
15
+ bundler (~> 1.9)
16
+ rake (~> 10.0)
17
+ sinatra-liveviews!
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Nathan Reed
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all 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,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,22 @@
1
+ # Sinatra::Liveviews
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ ```ruby
8
+ gem 'sinatra-liveviews'
9
+ ```
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install sinatra-liveviews
18
+
19
+ ## Usage
20
+
21
+
22
+
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.pattern = "test/*_test.rb"
7
+ end
8
+
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "sinatra/liveviews"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'sinatra'
4
+ gem 'sinatra-contrib'
5
+ gem 'sqlite3'
6
+ gem 'sequel'
7
+ gem "sinatra-liveviews", :path => "/Users/reednj/Documents/dev/sinatra-live-pages/gem/sinatra-liveviews"
@@ -0,0 +1,57 @@
1
+ PATH
2
+ remote: /Users/reednj/Documents/dev/sinatra-live-pages/gem/sinatra-liveviews
3
+ specs:
4
+ sinatra-liveviews (0.1.0)
5
+ json
6
+ sinatra
7
+ sinatra-websocket
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ addressable (2.3.8)
13
+ backports (3.6.8)
14
+ daemons (1.2.3)
15
+ em-websocket (0.3.8)
16
+ addressable (>= 2.1.1)
17
+ eventmachine (>= 0.12.9)
18
+ eventmachine (1.0.8)
19
+ json (1.8.1)
20
+ multi_json (1.11.2)
21
+ rack (1.6.4)
22
+ rack-protection (1.5.3)
23
+ rack
24
+ rack-test (0.6.3)
25
+ rack (>= 1.0)
26
+ sequel (4.32.0)
27
+ sinatra (1.4.6)
28
+ rack (~> 1.4)
29
+ rack-protection (~> 1.4)
30
+ tilt (>= 1.3, < 3)
31
+ sinatra-contrib (1.4.6)
32
+ backports (>= 2.0)
33
+ multi_json
34
+ rack-protection
35
+ rack-test
36
+ sinatra (~> 1.4.0)
37
+ tilt (>= 1.3, < 3)
38
+ sinatra-websocket (0.3.1)
39
+ em-websocket (~> 0.3.6)
40
+ eventmachine
41
+ thin (>= 1.3.1, < 2.0.0)
42
+ sqlite3 (1.3.11)
43
+ thin (1.6.4)
44
+ daemons (~> 1.0, >= 1.0.9)
45
+ eventmachine (~> 1.0, >= 1.0.4)
46
+ rack (~> 1.0)
47
+ tilt (1.4.1)
48
+
49
+ PLATFORMS
50
+ ruby
51
+
52
+ DEPENDENCIES
53
+ sequel
54
+ sinatra
55
+ sinatra-contrib
56
+ sinatra-liveviews!
57
+ sqlite3
@@ -0,0 +1,36 @@
1
+
2
+ require 'sinatra'
3
+
4
+ if development?
5
+ require 'bundler'
6
+ require 'sinatra/reloader'
7
+ Bundler.require
8
+ end
9
+
10
+ require 'sinatra/liveviews'
11
+ require './lib/model'
12
+
13
+ configure :development do
14
+ also_reload './lib/model.rb'
15
+ end
16
+
17
+ get '/' do
18
+ redirect to('/admin/stats')
19
+ end
20
+
21
+ get '/admin/stats' do
22
+ erb :home
23
+ end
24
+
25
+ live '/admin/stats' do |document|
26
+
27
+ document.on_load do
28
+ document.element('#js-count').text = 'ready'
29
+ end
30
+
31
+ UserScore.where(:user_id => 1).on_count_change do |scores|
32
+ document.element('#js-count').text = "#{scores.count} records"
33
+ document.element('#js-sum').text = "total: #{scores.sum(:score).round}"
34
+ document.element('#js-avg').text = "avg: #{scores.avg(:score).round(2)}"
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.require
5
+
6
+ require './app'
7
+ run Sinatra::Application
@@ -0,0 +1,14 @@
1
+ require 'sequel'
2
+ require 'sqlite3'
3
+
4
+ DB = Sequel.sqlite './liveviews.demo.db'
5
+
6
+ DB.create_table? :user_scores do
7
+ primary_key :id
8
+ Integer :user_id, :null => false
9
+ Float :score
10
+ end
11
+
12
+ class UserScore < Sequel::Model
13
+
14
+ end
@@ -0,0 +1,23 @@
1
+ require './lib/model'
2
+
3
+ class App
4
+ def main
5
+ @score_offset = rand() * 50
6
+
7
+ loop do
8
+ s = insert_random
9
+ puts s.score.round(2)
10
+ sleep 0.34
11
+ end
12
+ end
13
+
14
+ def insert_random
15
+ s = UserScore.new
16
+ s.user_id = (rand() * 10).to_i
17
+ s.score = rand() * (100 - @score_offset ) + @score_offset
18
+ s.save_changes
19
+ return s
20
+ end
21
+ end
22
+
23
+ App.new.main
@@ -0,0 +1,21 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
+ <meta name='viewport' content='width=device-width' />
6
+
7
+ <script type='text/javascript' src='https://code.jquery.com/jquery-2.2.2.min.js'></script>
8
+ <script type='text/javascript' src='/sinatra/liveviews.js'></script>
9
+
10
+ </head>
11
+
12
+ <body>
13
+
14
+ <div style='font-family: arial; margin: 16px;'>
15
+ <h1 id='js-count'>...</h1>
16
+ <h1 id='js-sum'>...</h1>
17
+ <h1 id='js-avg'>...</h1>
18
+ </div>
19
+
20
+ </body>
21
+ </html>
@@ -0,0 +1,27 @@
1
+ # we don't actually want this to be dependent on sequel, so only add
2
+ # the extensions if its already been included in the app
3
+ if defined? Sequel
4
+
5
+ class Sequel::Dataset
6
+ def on_count_change
7
+ Thread.new do
8
+ loop do
9
+ await_count_change
10
+ yield(self)
11
+ end
12
+ end
13
+ end
14
+
15
+ def await_count_change
16
+ polling_interval = 1.5
17
+ current_count = self.count
18
+
19
+ loop do
20
+ break if self.count != current_count
21
+ sleep polling_interval
22
+ end
23
+
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,70 @@
1
+ // Basic javascript Class object. No inheritance or anything like that
2
+ // but allows the nice encapsulation of properties and methods.
3
+ //
4
+ // Takes as its only argument an object with a set of methods for the
5
+ // class. If there is a method called initialize, it will be used as the
6
+ // constructor.
7
+ //
8
+ // Usage:
9
+ //
10
+ // var Logger = new Class({
11
+ // initialize: function(name) {
12
+ // this.name = name;
13
+ // console.log('init: ' + this.name);
14
+ // },
15
+ //
16
+ // log: function(txt) {
17
+ // console.log('log ' + this.name + ': ' + txt);
18
+ // }
19
+ // });
20
+ //
21
+ var Class = function (obj) {
22
+ obj = obj || {};
23
+
24
+ var $Class = function () {
25
+ var emptyFn = (function () { });
26
+ (obj.initialize || emptyFn).apply(this, arguments);
27
+ };
28
+
29
+ for (k in obj) {
30
+ if (obj.hasOwnProperty(k)) {
31
+ var fn = obj[k];
32
+ if (typeof fn == 'function') {
33
+ $Class.prototype[k] = fn;
34
+ }
35
+ }
36
+ };
37
+
38
+ return $Class;
39
+ };
40
+
41
+ (function () {
42
+
43
+
44
+ var implement = function(name, fn) {
45
+ if(!this.prototype[name])
46
+ this.prototype[name] = fn;
47
+
48
+ return this;
49
+ };
50
+
51
+ if(!Function.prototype.implement) {
52
+ Function.prototype.implement = implement;
53
+ }
54
+
55
+ if(!Element.prototype.implement) {
56
+ Element.implement = implement;
57
+ Element.prototype.implement = implement;
58
+ }
59
+
60
+ })();
61
+
62
+ (function() {
63
+ Function.implement('delay', function(time_ms, scope) {
64
+ return setTimeout(this.bind(scope), time_ms);
65
+ });
66
+
67
+ Function.implement('periodical', function(time_ms, scope) {
68
+ return setInterval(this.bind(scope), time_ms);
69
+ });
70
+ })();
@@ -0,0 +1,22 @@
1
+ var LivePageHandler = new Class({
2
+ initialize: function() {
3
+ var referrer_url = encodeURIComponent(document.location.href);
4
+ var socket_url = JSONSocket.websocketUrlForPath('/sinatra/liveviews/ws');
5
+
6
+ this.websocket = new JSONSocket({
7
+ url: socket_url + '?url=' + referrer_url,
8
+ on_exec: function(data) {
9
+ $(data.selector)[data.method](data.content);
10
+ },
11
+
12
+ on_message: function(data) {
13
+ console.log('live pages: ' + data.content);
14
+ }
15
+
16
+ });
17
+ }
18
+ });
19
+
20
+ $(document).ready(function() {
21
+ window._live_page_handler = new LivePageHandler();
22
+ });
@@ -0,0 +1,135 @@
1
+
2
+ if(typeof WebSocket === 'undefined') {
3
+ // if websockets are not supported, then we just create
4
+ // a dummy one that does nothing, but gives no errors
5
+ WebSocket = new Class({
6
+ initialize: function() {},
7
+ send: function() {}
8
+ });
9
+ }
10
+
11
+
12
+ var JSONSocket = new Class({
13
+ initialize: function(options) {
14
+ this.options = options || {};
15
+ this.options.url = this.options.url || null;
16
+ this.options.onOpen = this.options.onOpen || function() {};
17
+ this.options.onClose = this.options.onClose || function() {};
18
+
19
+ this.options.autoreconnect = this.options.autoreconnect === false ? false : true;
20
+ this.options.autoconnect = this.options.autoconnect === false ? false : true;
21
+ this.options.connectWait = 1;
22
+
23
+ this._stats = {in: 0, out: 0};
24
+
25
+ if(this.options.autoconnect) {
26
+ this.initSocket();
27
+ }
28
+ },
29
+
30
+ initSocket: function() {
31
+ this.ws = new WebSocket(this.options.url);
32
+ this.ws.onopen = this.onOpen.bind(this);
33
+ this.ws.onclose = this.onClose.bind(this);
34
+ this.ws.onmessage = function(e) {
35
+ this.onMessage(JSON.parse(e.data));
36
+ }.bind(this);
37
+ },
38
+
39
+ onOpen: function() {
40
+ this.options.connectWait = 1;
41
+ this.options.onOpen(this, this.ws);
42
+ },
43
+
44
+ onClose: function() {
45
+ this.options.onClose(this, this.ws);
46
+
47
+ if(this.options.autoreconnect === true) {
48
+ this.open.delay(this.options.connectWait * 1000, this);
49
+ this.options.connectWait *= 2;
50
+
51
+ if(this.options.connectWait > 30) {
52
+ this.options.connectWait = 30;
53
+ }
54
+
55
+ }
56
+ },
57
+
58
+ onMessage: function(msg) {
59
+ if(msg.event && typeof msg.event == 'string') {
60
+ this._stats.in++;
61
+ (this.eventNameToFunction(msg.event))(msg.data);
62
+ }
63
+ },
64
+
65
+ send: function(eventType, data) {
66
+ if(this.isConnected()) {
67
+ var str = JSON.encode({event: eventType, data: data});
68
+ this.ws.send(str);
69
+ this._stats.out++;
70
+ }
71
+ },
72
+
73
+ addEvents: function(events) {
74
+ Object.each(events, function(fn, eventType) {
75
+ this.addEvent(eventType, fn);
76
+ }.bind(this));
77
+
78
+ return this;
79
+ },
80
+
81
+ addEvent: function(eventType, fn) {
82
+
83
+ if(eventType && typeof fn == 'function') {
84
+ var fnName = 'on_' + eventType;
85
+
86
+ if(!this.options[fnName]) {
87
+ this.options[fnName] = fn;
88
+ } else if(typeof this.options[fnName] == 'function') {
89
+ var currentFn = this.options[fnName];
90
+ this.options[fnName] = function() {
91
+ currentFn.apply(this, arguments);
92
+ fn.apply(this, arguments);
93
+ };
94
+ }
95
+ }
96
+
97
+ return this;
98
+ },
99
+
100
+ eventNameToFunction: function(eventType) {
101
+ if(eventType) {
102
+ var fnName = 'on_' + eventType;
103
+ return this.options[fnName] || (function() {});
104
+ }
105
+ },
106
+
107
+ isConnected: function() {
108
+ WebSocket.OPEN = WebSocket.OPEN || 1; // turns out not all browsers define the state consts
109
+ return this.ws && this.ws.readyState === WebSocket.OPEN;
110
+ },
111
+
112
+ close: function() {
113
+ this.options.autoreconnect = false;
114
+ this.ws.close();
115
+ },
116
+
117
+ open: function() {
118
+ this.initSocket();
119
+ },
120
+
121
+ stats: function() {
122
+ return this._stats;
123
+ }
124
+ });
125
+
126
+ JSONSocket.websocketUrlForPath = function(path) {
127
+ var protocol = 'ws://';
128
+
129
+ if(document.location.protocol.indexOf('https') != -1) {
130
+ protocol = 'wss://';
131
+ }
132
+
133
+ return protocol + document.location.hostname + ':' + (document.location.port || '80') + (path || '/');;
134
+ };
135
+
@@ -0,0 +1,47 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/liveviews/version'
3
+
4
+ require_relative './liveviews/page-websocket'
5
+ require_relative '../sequel/sequel-extensions'
6
+
7
+ module LivePages
8
+ def live(url, options = {}, &block)
9
+ method_name = _method_name 'LIVE', url
10
+ _register_callback method_name, &block
11
+ end
12
+
13
+ def _method_name(verb, url)
14
+ "#{verb} #{url}".to_sym
15
+ end
16
+
17
+ def _register_callback(method_name, &block)
18
+ self.send :define_method, method_name, &block
19
+ end
20
+
21
+ def self.registered(app)
22
+ app.get '/sinatra/liveviews/ws' do
23
+ return 'websockets only' if !request.websocket?
24
+
25
+ request.websocket do |ws|
26
+ PageWebSocket.new ws, {
27
+ :app => self,
28
+ :url => params[:url] || request.referrer
29
+ }
30
+ end
31
+ end
32
+
33
+ app.get '/sinatra/liveviews.js' do
34
+ folder = File.join File.dirname(__FILE__), './js/'
35
+ js = ['extensions.js', 'socket.js', 'live-pages.js'].map do |file|
36
+ path = File.join folder, file
37
+ File.read path
38
+ end
39
+
40
+ return 200, {'Content-type' => 'text/javascript'}, js.join("\n")
41
+ end
42
+ end
43
+ end
44
+
45
+ module Sinatra
46
+ register LivePages
47
+ end
@@ -0,0 +1,109 @@
1
+ # A wrapper around the great sinatra-websocket gem (https://github.com/simulacre/sinatra-websocket) that
2
+ # allows for event based websockets
3
+ #
4
+ # Each message to the client should be a json object in the form {'event': string, 'data': obj }
5
+ # When received by the server the appropriate method for that event is called, if it exists. For
6
+ # example, the event 'setCell' would try to call the method on_set_cell with object contained
7
+ # in 'data'. You should subclass WebSocketHelper in order to implement these methods
8
+ #
9
+ # Nathan Reed (@reednj) 2013-08-21
10
+
11
+ require 'json'
12
+ require 'sinatra-websocket'
13
+
14
+ class WebSocketHelper
15
+ attr_accessor :latency
16
+
17
+ def initialize(ws)
18
+ self.latency = nil
19
+ # the sockets list is the list of all other WebSocketHelper classes
20
+ # for all other connections. This makes it easy to send a message to
21
+ # all connected clients
22
+ @sockets = SharedList.list
23
+ @ws = ws
24
+
25
+ @ws.onopen { self.on_open }
26
+ @ws.onclose { self.on_close }
27
+ @ws.onmessage { |msg|
28
+ d = JSON.parse(msg, {:symbolize_names => true})
29
+ if d != nil && d[:event] != nil
30
+ Thread.new { self.on_message(d[:event], d[:data]) }
31
+ end
32
+ }
33
+ end
34
+
35
+ def on_open
36
+ @sockets.push self
37
+ end
38
+
39
+ def on_message(event, data)
40
+ event_method = 'on_' + event.underscore
41
+
42
+ begin
43
+ simulate_latency if !self.latency.nil?
44
+ method(event_method).call(data) if self.respond_to? event_method
45
+ rescue => e
46
+ # if the subclass has defined an error handler, then use that if something has
47
+ # happened, otherwise we just rethrow it
48
+ if self.respond_to? 'handle_error'
49
+ handle_error(e, event_method, data)
50
+ else
51
+ raise
52
+ end
53
+ end
54
+ end
55
+
56
+ def on_close
57
+ @sockets.delete(self)
58
+ end
59
+
60
+ # send a message in to the current client
61
+ def send(event, data = {})
62
+ @ws.send({:event => event, :data => data}.to_json)
63
+ end
64
+
65
+ # sends a message to all connected clients, including the current client
66
+ def send_all(event, data)
67
+ EM.next_tick {
68
+ @sockets.each do |s|
69
+ s.send(event, data)
70
+ end
71
+ }
72
+ end
73
+
74
+ # sends a message to all connected clients, *except* the current one
75
+ def send_others(event, data)
76
+ EM.next_tick {
77
+ @sockets.each do |s|
78
+ s.send(event, data) if s != self
79
+ end
80
+ }
81
+ end
82
+
83
+ def simulate_latency
84
+ return if self.latency.nil?
85
+ factor = ((rand() * 0.2 - 0.1) + 1) # 0.9 to 1.1
86
+ delay = self.latency * factor
87
+ sleep delay
88
+ end
89
+
90
+ #def handle_error(e, path)
91
+ end
92
+
93
+ class String
94
+ def underscore
95
+ self.gsub(/::/, '/').
96
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
97
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
98
+ tr("-", "_").
99
+ downcase
100
+ end
101
+ end
102
+
103
+ class SharedList
104
+ @@data = nil
105
+ def self.list
106
+ @@data = [] if @@data == nil
107
+ return @@data
108
+ end
109
+ end
@@ -0,0 +1,91 @@
1
+ require_relative './json-websocket'
2
+
3
+ class PageWebSocket < WebSocketHelper
4
+
5
+ def initialize(ws, options = {})
6
+ super(ws)
7
+
8
+ # todo: validate the url and the app instances
9
+ @app = options[:app]
10
+ @url = options[:url]
11
+ end
12
+
13
+ def on_open
14
+ super
15
+
16
+ uri = URI.parse(@url)
17
+ path = uri.path
18
+ method_name = @app.class._method_name('LIVE', path)
19
+
20
+ if @app.respond_to? method_name
21
+ @app.send(method_name, document)
22
+ else
23
+ # send an error back to the client
24
+ self.send 'message', { :content => "no live handler for #{path}" }
25
+ end
26
+
27
+ end
28
+
29
+ def on_close
30
+ super
31
+ end
32
+
33
+ def document
34
+ if @document.nil?
35
+ @document = ClientDocument.new(self)
36
+ @document.location = @url
37
+ end
38
+
39
+ return @document
40
+ end
41
+
42
+ end
43
+
44
+ class ClientDocument
45
+ attr_accessor :location
46
+
47
+ def initialize(client)
48
+ raise 'client must be a WebSocketHelper' unless client.is_a? WebSocketHelper
49
+ @client = client
50
+ end
51
+
52
+ def element(selector)
53
+ ClientElement.new(selector, @client)
54
+ end
55
+
56
+ def on_load
57
+ # maybe later we can do something else with this, but for now
58
+ # just call the load method straight away
59
+ yield()
60
+ end
61
+ end
62
+
63
+ class ClientElement
64
+ attr_accessor :selector
65
+
66
+ def initialize(selector, client)
67
+ raise 'client must be a WebSocketHelper' unless client.is_a? WebSocketHelper
68
+
69
+ @client = client
70
+ self.selector = selector.to_s
71
+ end
72
+
73
+ def execute(method, content)
74
+ @client.send('exec', {
75
+ :selector => self.selector,
76
+ :method => method,
77
+ :content => content.to_s
78
+ })
79
+ end
80
+
81
+ def text=(s)
82
+ self.execute 'text', s
83
+ end
84
+
85
+ def html=(s)
86
+ self.execute 'html', s
87
+ end
88
+
89
+ end
90
+
91
+
@@ -0,0 +1,5 @@
1
+ module Sinatra
2
+ module Liveviews
3
+ VERSION = "0.5.0"
4
+ end
5
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sinatra/liveviews/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sinatra-liveviews"
8
+ spec.version = Sinatra::Liveviews::VERSION
9
+ spec.authors = ["Nathan Reed"]
10
+ spec.email = ["reednj@gmail.com"]
11
+
12
+ spec.summary = %q{ create dashboards in sinatra that instantly update when the database changes }
13
+ spec.homepage = "https://github.com/reednj/sinatra-liveviews"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.9"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+
25
+ spec.add_runtime_dependency "json"
26
+ spec.add_runtime_dependency "sinatra"
27
+ spec.add_runtime_dependency "sinatra-websocket"
28
+
29
+ end
@@ -0,0 +1,19 @@
1
+ ENV['RACK_ENV'] = 'development'
2
+
3
+ require 'rubygems'
4
+ require 'minitest/autorun'
5
+ require 'rack/test'
6
+ require 'test/unit'
7
+
8
+ require_relative './app'
9
+
10
+ class LiveViewsTest < Test::Unit::TestCase
11
+ include Rack::Test::Methods
12
+
13
+ def app
14
+ Sinatra::Application
15
+ end
16
+
17
+
18
+ end
19
+
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinatra-liveviews
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - Nathan Reed
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-03-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sinatra-websocket
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
85
+ - reednj@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - Gemfile
92
+ - Gemfile.lock
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - bin/console
97
+ - bin/setup
98
+ - demo/Gemfile
99
+ - demo/Gemfile.lock
100
+ - demo/app.rb
101
+ - demo/config.ru
102
+ - demo/lib/model.rb
103
+ - demo/test-inserts.rb
104
+ - demo/views/home.erb
105
+ - lib/sequel/sequel-extensions.rb
106
+ - lib/sinatra/js/extensions.js
107
+ - lib/sinatra/js/live-pages.js
108
+ - lib/sinatra/js/socket.js
109
+ - lib/sinatra/liveviews.rb
110
+ - lib/sinatra/liveviews/json-websocket.rb
111
+ - lib/sinatra/liveviews/page-websocket.rb
112
+ - lib/sinatra/liveviews/version.rb
113
+ - sinatra-liveviews.gemspec
114
+ - test/liveviews_test.rb
115
+ homepage: https://github.com/reednj/sinatra-liveviews
116
+ licenses:
117
+ - MIT
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 2.4.5
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: create dashboards in sinatra that instantly update when the database changes
139
+ test_files:
140
+ - test/liveviews_test.rb