sinatra-liveviews 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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