pakyow 0.10.2 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/LICENSE +1 -1
  4. data/README.md +4 -1
  5. data/bin/pakyow +2 -16
  6. data/lib/generators/pakyow/app/app_generator.rb +46 -13
  7. data/lib/generators/pakyow/app/templates/Gemfile +8 -7
  8. data/lib/generators/pakyow/app/templates/README.md +2 -2
  9. data/lib/generators/pakyow/app/templates/app/setup.rb +10 -19
  10. data/lib/generators/pakyow/app/templates/app/views/_templates/default.html +3 -1
  11. data/lib/generators/pakyow/app/templates/env +1 -0
  12. data/lib/generators/pakyow/app/templates/env.example +3 -0
  13. data/lib/generators/pakyow/app/templates/gitignore +8 -0
  14. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/fastlink.js +3 -2
  15. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/fastlink.min.js +1 -1
  16. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/ga.js +34 -0
  17. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/ga.min.js +1 -0
  18. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/modal.js +1 -1
  19. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/modal.min.js +1 -1
  20. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/mutable.js +5 -1
  21. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/mutable.min.js +1 -1
  22. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/navigator.js +10 -6
  23. data/lib/generators/pakyow/app/templates/public/scripts/ring/components/navigator.min.js +1 -1
  24. data/lib/generators/pakyow/app/templates/public/scripts/ring/pakyow.js +175 -45
  25. data/lib/generators/pakyow/app/templates/public/scripts/ring/pakyow.min.js +1 -1
  26. data/lib/generators/pakyow/app/templates/rspec +3 -0
  27. data/lib/pakyow.rb +1 -1
  28. data/lib/pakyow/command_line_interface.rb +79 -0
  29. data/lib/pakyow/commands/console.rb +27 -0
  30. data/lib/pakyow/commands/console_methods.rb +10 -0
  31. data/lib/pakyow/commands/server.rb +38 -0
  32. data/lib/pakyow/version.rb +3 -0
  33. metadata +58 -27
  34. data/lib/commands/USAGE +0 -9
  35. data/lib/commands/USAGE-CONSOLE +0 -12
  36. data/lib/commands/USAGE-NEW +0 -11
  37. data/lib/commands/USAGE-SERVER +0 -16
  38. data/lib/commands/console.rb +0 -17
  39. data/lib/commands/server.rb +0 -27
  40. data/lib/version.rb +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 35ae9d36c47c1b8d13ba46589a5508496c0b09ca
4
- data.tar.gz: 3468a1d1e5b3482250e67d0190f1cf3975b76dd9
3
+ metadata.gz: 199c2331bc67c6b913a5408523fc60019770d551
4
+ data.tar.gz: b6e2c05f9d55ca795ffb44cf9ed0f7a21c22837a
5
5
  SHA512:
6
- metadata.gz: 420ab1dd7017d15645a8625e44d0a191b15c0659fb445ece91d95257590c99426e2647bca89b601881e3709c4491d215632620dd3ab173708f4bee7779511c0f
7
- data.tar.gz: 7e284f90e783752600a739324d61e082c4438695a25a26b6704ede2f6cc1482a3b835c0be1c6fb2562328c140cbf031d89e77657ceaeb98ceadd86610f891efc
6
+ metadata.gz: 1f0bfd078e041e64094ecbba209afdf4b761d760afcabe795b0b83363f112edc20ac1af5167ce63ad5bae5ab429f0dc683747d7db843a6e09fc7c4b0b60e2c46
7
+ data.tar.gz: 8191df207e1ec7e2211dbe8252c714c7b62b7a078b6cefa88c85ed493686030b1720c9d47b9f30f220faa98443afbe8fb2b88638c987f1796b0502a1b3266926
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ # 0.11.0
2
+
3
+ * Generated app updates:
4
+ * Uses dotenv for config management
5
+ * Breaks middleware into pluggable pieces
6
+ * Dynamically sets app name in config
7
+ * Creates a random session secret
8
+ * Updates Ring to 0.2.4
9
+ * Evaluates app template as erb
10
+ * Improves CLI, including new `version` command
11
+ * Moves everything into the Pakyow namespace
12
+ * Adds ASCII art to `server` command \o/
13
+
1
14
  # 0.10.2 / 2015-11-15
2
15
 
3
16
  * Updates Ring to latest in generator
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2015 Bryan Powell
1
+ Copyright (c) 2011-2016 Bryan Powell
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  [gem]: https://rubygems.org/gems/pakyow
2
2
  [travis]: https://travis-ci.org/pakyow/pakyow
3
3
  [gemnasium]: https://gemnasium.com/pakyow/pakyow
4
+ [inchpages]: http://inch-ci.org/github/pakyow/pakyow
5
+ [codeclimate]: https://codeclimate.com/github/pakyow/pakyow
4
6
 
5
7
  # Pakyow Framework [![Gitter chat](https://badges.gitter.im/pakyow/chat.svg)](https://gitter.im/pakyow/chat)
6
8
 
@@ -46,6 +48,7 @@ implements this protocol on the backend for initial rendering and in
46
48
  [![Gem Version](https://badge.fury.io/rb/pakyow.svg)][gem]
47
49
  [![Build Status](https://travis-ci.org/pakyow/pakyow.svg?branch=master)][travis]
48
50
  [![Dependency Status](https://gemnasium.com/pakyow/pakyow.svg)][gemnasium]
51
+ [![Inline docs](http://inch-ci.org/github/pakyow/pakyow.svg?branch=master&style=flat)][inchpages]
49
52
 
50
53
  ---
51
54
 
@@ -61,7 +64,7 @@ implements this protocol on the backend for initial rendering and in
61
64
 
62
65
  3. Move to the new directory and start the server:
63
66
 
64
- `cd webapp; pakyow server`
67
+ `cd webapp; bundle exec pakyow server`
65
68
 
66
69
  4. You'll find your project running at [http://localhost:3000](http://localhost:3000)!
67
70
 
data/bin/pakyow CHANGED
@@ -1,18 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- PAK_PATH = File.expand_path('../../lib', __FILE__)
4
-
5
- case ARGV.first
6
- when 'new'
7
- ARGV.shift
8
- require File.join(PAK_PATH, 'generators/pakyow/app/app_generator')
9
- Pakyow::Generators::AppGenerator.start
10
- when 'server', 's'
11
- ARGV.shift
12
- require File.join(PAK_PATH, 'commands/server')
13
- when 'console', 'c'
14
- ARGV.shift
15
- require File.join(PAK_PATH, 'commands/console')
16
- when '--help', '-h', nil
17
- puts File.open(File.join(PAK_PATH, 'commands/USAGE')).read
18
- end
3
+ require "pakyow/command_line_interface"
4
+ Pakyow::CommandLineInterface.start
@@ -1,20 +1,25 @@
1
+ require 'erb'
1
2
  require 'fileutils'
3
+ require 'securerandom'
4
+ require 'pakyow/version.rb'
2
5
 
3
6
  module Pakyow
4
7
  module Generators
5
8
  class AppGenerator
6
9
  class << self
7
- def start
8
- case ARGV.first
9
- when '--help', '-h', nil
10
- puts File.open(File.join(PAK_PATH, 'commands/USAGE-NEW')).read
11
- else
12
- generator = self.new(ARGV.first)
13
- generator.build
14
- end
10
+ def start(destination)
11
+ generator = self.new(destination)
12
+ generator.build
15
13
  end
16
14
  end
17
15
 
16
+ FILENAME_TRANSLATIONS = {
17
+ 'rspec' => '.rspec',
18
+ 'gitignore' => '.gitignore',
19
+ 'env' => '.env',
20
+ 'env.example' => '.env.example'
21
+ }
22
+
18
23
  def initialize(dest)
19
24
  @src = "#{File.expand_path('../', __FILE__)}/templates/."
20
25
  @dest = dest
@@ -38,23 +43,51 @@ module Pakyow
38
43
  end
39
44
 
40
45
  exec
41
- puts "Done! Run `cd #{@dest}; pakyow server` to get started!"
46
+ puts "Done! Run `cd #{@dest}; bundle exec pakyow server` to get started!"
42
47
  end
43
48
 
44
49
  protected
45
50
 
46
- # copies src files to dest
47
51
  def copy
48
- FileUtils.cp_r(@src, @dest)
52
+ FileUtils.mkdir(@dest) unless File.exists?(@dest)
53
+
54
+ Dir.glob(File.join(@src, '**', '*')).each do |path|
55
+ relative_path = path[@src.length..-1]
56
+ generated_path = File.join(@dest, File.dirname(relative_path), translated_filename(File.basename(relative_path)))
57
+
58
+ if File.directory?(path)
59
+ FileUtils.mkdir(generated_path)
60
+ next
61
+ end
62
+
63
+ erb = ERB.new(File.read(path))
64
+ File.open(generated_path, 'w') { |f| f.write(erb.result(binding)) }
65
+ end
49
66
  end
50
67
 
51
- # performs and other setup (e.g. bundle install)
52
68
  def exec
53
69
  FileUtils.cd(@dest) do
54
70
  puts "Running `bundle install` in #{Dir.pwd}"
55
- system("bundle install")
71
+ system("bundle install --binstubs")
56
72
  end
57
73
  end
74
+
75
+ def generating_locally?
76
+ local_pakyow = Gem::Specification.sort_by{ |g| [g.name.downcase, g.version] }.group_by{ |g| g.name }.detect{|k,v| k == 'pakyow'}
77
+ !local_pakyow || local_pakyow.last.last.version < Gem::Version.new(Pakyow::VERSION)
78
+ end
79
+
80
+ def translated_filename(filename)
81
+ FILENAME_TRANSLATIONS.fetch(filename, filename)
82
+ end
83
+
84
+ def generate_session_secret
85
+ SecureRandom.hex(64)
86
+ end
87
+
88
+ def app_name
89
+ File.basename(@dest)
90
+ end
58
91
  end
59
92
  end
60
93
  end
@@ -1,15 +1,16 @@
1
1
  source 'https://rubygems.org'
2
-
3
- # live on the edge and uncomment this line
4
- # gem 'pakyow', github: 'pakyow/pakyow'
5
-
6
- # use the latest released version
7
- gem 'pakyow', '~> 0.10'
8
-
2
+ <% if generating_locally? %>
3
+ gem 'pakyow', github: 'pakyow/pakyow'
4
+ <% else %>
5
+ gem 'pakyow', '~> <%= Pakyow::VERSION %>'
6
+ <% end %>
9
7
  # app server
10
8
  gem 'puma', platforms: :ruby
11
9
  gem 'thin', platforms: :mswin
12
10
 
11
+ # use dotenv to load environment variables
12
+ gem 'dotenv'
13
+
13
14
  group :test do
14
15
  gem 'rspec'
15
16
  end
@@ -4,13 +4,13 @@ This is a Pakyow v0.10 project.
4
4
 
5
5
  Start the server:
6
6
 
7
- `pakyow server`
7
+ `bundle exec pakyow server`
8
8
 
9
9
  You'll find your app running at [http://localhost:3000](http://localhost:3000)!
10
10
 
11
11
  Need to interact with your code? Fire up a console:
12
12
 
13
- `pakyow console`
13
+ `bundle exec pakyow console`
14
14
 
15
15
  # Next Steps
16
16
 
@@ -2,32 +2,23 @@ require 'bundler/setup'
2
2
  require 'pakyow'
3
3
 
4
4
  Pakyow::App.define do
5
- configure :global do
5
+ configure do
6
6
  Bundler.require :default, Pakyow::Config.env
7
7
 
8
- # put global config here and they'll be available across environments
9
- app.name = 'Pakyow'
10
- end
11
-
12
- configure :development do
13
- # put development config here
14
- end
8
+ if defined?(Dotenv)
9
+ env_path = ".env.#{Pakyow::Config.env}"
10
+ Dotenv.load env_path if File.exist?(env_path)
11
+ Dotenv.load
12
+ end
15
13
 
16
- configure :prototype do
17
- # an environment for running the front-end prototype with no backend
18
- app.ignore_routes = true
14
+ app.name = '<%= app_name %>'
19
15
  end
20
16
 
21
- configure :staging do
22
- # put your staging config here
17
+ configure :development do
18
+ # development config goes here
23
19
  end
24
20
 
25
21
  configure :production do
26
- # put your production config here
27
- end
28
-
29
- middleware do |builder|
30
- # TODO: you will most definitely want to change this secret
31
- builder.use Rack::Session::Cookie, key: "#{Pakyow::Config.app.name}.session", secret: 'sekret'
22
+ # production config goes here
32
23
  end
33
24
  end
@@ -2,7 +2,7 @@
2
2
  <html>
3
3
  <head>
4
4
  <title>
5
- Pakyow
5
+ <%= app_name %>
6
6
  </title>
7
7
 
8
8
  <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0" name="viewport">
@@ -13,6 +13,8 @@
13
13
  <link rel="stylesheet" type="text/css" href="/styles/pakyow-css/theme.css">
14
14
 
15
15
  <script src="/scripts/ring/pakyow.js"></script>
16
+
17
+ <meta charset="UTF-8">
16
18
  </head>
17
19
 
18
20
  <body>
@@ -0,0 +1 @@
1
+ SESSION_SECRET=<%= generate_session_secret %>
@@ -0,0 +1,3 @@
1
+ # Please place the following environment variables in your local `.env` file.
2
+
3
+ SESSION_SECRET=sekret
@@ -0,0 +1,8 @@
1
+ # ignore bundler config
2
+ .bundle
3
+
4
+ # ignore environment files
5
+ .env*
6
+
7
+ # ignore binstubs
8
+ bin
@@ -1,8 +1,9 @@
1
1
  pw.component.register('fastlink', function (view, config) {
2
- var that = this;
3
-
4
2
  if (window.history) {
5
3
  view.node.addEventListener('click', function (evt) {
4
+ // don't break open in new tab!
5
+ if (evt.metaKey || evt.ctrlKey) return;
6
+
6
7
  evt.preventDefault();
7
8
  window.history.pushState({ uri: this.href }, this.href, this.href);
8
9
  return false;
@@ -1 +1 @@
1
- pw.component.register("fastlink",function(t,e){window.history&&t.node.addEventListener("click",function(t){return t.preventDefault(),window.history.pushState({uri:this.href},this.href,this.href),!1})});
1
+ pw.component.register("fastlink",function(t,e){window.history&&t.node.addEventListener("click",function(t){return t.metaKey||t.ctrlKey?void 0:(t.preventDefault(),window.history.pushState({uri:this.href},this.href,this.href),!1)})});
@@ -0,0 +1,34 @@
1
+ /*
2
+ Ring.js - Google Analytics Component
3
+
4
+ Sets up Google Analytics and tracks the immediate pageview along with any
5
+ pageviews that occur over a WebSocket connection with Navigator. To use,
6
+ attach the component to the <body> tag and configure the trackingId:
7
+
8
+ <body data-ui="ga" data-config="trackingId: yourTrackingIdHere">
9
+
10
+ It will automatically ignore pageviews that occur locally.
11
+ */
12
+
13
+ pw.component.register('ga', function (view, config) {
14
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
15
+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
16
+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
17
+ })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
18
+ ga('create', config.trackingId, 'auto');
19
+
20
+ this.track = function (uri) {
21
+ if (document.domain.indexOf('local') != -1) {
22
+ return;
23
+ }
24
+
25
+ ga('send', 'pageview', uri);
26
+ };
27
+
28
+ this.listen('navigator:change', function (payload) {
29
+ this.track(payload.uri);
30
+ });
31
+
32
+ // track the current pageview
33
+ this.track(location.pathname);
34
+ });
@@ -0,0 +1 @@
1
+ pw.component.register("ga",function(t,n){!function(t,n,e,a,c,i,o){t.GoogleAnalyticsObject=c,t[c]=t[c]||function(){(t[c].q=t[c].q||[]).push(arguments)},t[c].l=1*new Date,i=n.createElement(e),o=n.getElementsByTagName(e)[0],i.async=1,i.src=a,o.parentNode.insertBefore(i,o)}(window,document,"script","//www.google-analytics.com/analytics.js","ga"),ga("create",n.trackingId,"auto"),this.track=function(t){-1==document.domain.indexOf("local")&&ga("send","pageview",t)},this.listen("navigator:change",function(t){this.track(t.uri)}),this.track(location.pathname)});
@@ -60,7 +60,7 @@ pw.component.register('modal', function (view, config, name, id) {
60
60
 
61
61
  view.node.addEventListener('click', function (evt) {
62
62
  evt.preventDefault();
63
- self.load(this.href);
63
+ self.load(config.href || this.href);
64
64
  return false;
65
65
  });
66
66
 
@@ -1 +1 @@
1
- pw.component.register("modal",function(t,e,n,o){var a,i,d,l=this,r="modal:"+o;(d=document.querySelector('*[data-template="ui-modal-blinder"]'))&&(d=d.cloneNode(!0)),this.listen(r+":navigator:enter",function(t){a||(d?(a=d.cloneNode(!0),i=a.querySelector('*[data-template="ui-modal-content"]'),document.body.appendChild(a),a.removeAttribute("data-template"),i.removeAttribute("data-template")):(a=document.createElement("DIV"),a.classList.add("ui-modal-blinder"),i=document.createElement("DIV"),i.classList.add("ui-modal"),a.appendChild(i),document.body.appendChild(a)),a.addEventListener("click",function(t){if(t.target===a){t.preventDefault(),l.close();var e=window.location.pathname,n={uri:e};window.history.pushState(n,e,e)}})),i.innerHTML=t.body,pw.component.findAndInit(a),a.classList.add("ui-appear")}),this.listen(r+":navigator:exit",function(){l.close()}),this.listen(r+":navigator:boot",function(t){l.load(t)}),t.node.addEventListener("click",function(t){return t.preventDefault(),l.load(this.href),!1}),this.load=function(t){if(!window.socket)return void(document.location=t);var n={uri:t,context:"modal:"+o};e.container&&(n.container=e.container),e.partial&&(n.partial=e.partial),window.history.pushState(n,t,t)},this.close=function(){a&&i&&(pw.node.remove(a),a=null,i=null)}});
1
+ pw.component.register("modal",function(t,e,n,o){var a,i,d,l=this,r="modal:"+o;(d=document.querySelector('*[data-template="ui-modal-blinder"]'))&&(d=d.cloneNode(!0)),this.listen(r+":navigator:enter",function(t){a||(d?(a=d.cloneNode(!0),i=a.querySelector('*[data-template="ui-modal-content"]'),document.body.appendChild(a),a.removeAttribute("data-template"),i.removeAttribute("data-template")):(a=document.createElement("DIV"),a.classList.add("ui-modal-blinder"),i=document.createElement("DIV"),i.classList.add("ui-modal"),a.appendChild(i),document.body.appendChild(a)),a.addEventListener("click",function(t){if(t.target===a){t.preventDefault(),l.close();var e=window.location.pathname,n={uri:e};window.history.pushState(n,e,e)}})),i.innerHTML=t.body,pw.component.findAndInit(a),a.classList.add("ui-appear")}),this.listen(r+":navigator:exit",function(){l.close()}),this.listen(r+":navigator:boot",function(t){l.load(t)}),t.node.addEventListener("click",function(t){return t.preventDefault(),l.load(e.href||this.href),!1}),this.load=function(t){if(!window.socket)return void(document.location=t);var n={uri:t,context:"modal:"+o};e.container&&(n.container=e.container),e.partial&&(n.partial=e.partial),window.history.pushState(n,t,t)},this.close=function(){a&&i&&(pw.node.remove(a),a=null,i=null)}});
@@ -54,13 +54,17 @@ pw.component.register('mutable', function (view, config) {
54
54
  }
55
55
  } else if (res.status === 400) {
56
56
  // bad request
57
+ pw.component.broadcast('response:received', { response: res });
57
58
  return;
58
59
  } else {
59
60
  self.state.rollback();
60
61
  }
61
62
 
62
63
  pw.component.broadcast('response:received', { response: res });
63
- self.revert();
64
+
65
+ if (config.revert !== 'false') {
66
+ self.revert();
67
+ }
64
68
  });
65
69
  }
66
70
  });
@@ -1 +1 @@
1
- pw.component.register("mutable",function(e,t){this.mutation=function(t){if(!window.socket)return void e.node.submit();var o=pw.util.dup(t);delete o.__nested,delete o.scope,delete o.id;var n={action:"call-route"};if("FORM"===e.node.tagName){if(e.node.querySelector('input[type="file"]'))return void e.node.submit();var i,r=e.node.querySelector('input[name="_method"]');i=r?r.value:e.node.getAttribute("method"),n.method=i,n.uri=e.node.getAttribute("action"),n.input=pw.node.serialize(e.node)}else{var a={};a[t.scope]=o,n.input=a}var d=this;window.socket.send(n,function(e){if(302===e.status){var t=e.headers.Location;t!=window.location.pathname||window.context&&"default"===window.context.name?history.pushState({uri:t},t,t):history.pushState({uri:t},t,t)}else{if(400===e.status)return;d.state.rollback()}pw.component.broadcast("response:received",{response:e}),d.revert()})}});
1
+ pw.component.register("mutable",function(e,t){this.mutation=function(o){if(!window.socket)return void e.node.submit();var n=pw.util.dup(o);delete n.__nested,delete n.scope,delete n.id;var i={action:"call-route"};if("FORM"===e.node.tagName){if(e.node.querySelector('input[type="file"]'))return void e.node.submit();var r,s=e.node.querySelector('input[name="_method"]');r=s?s.value:e.node.getAttribute("method"),i.method=r,i.uri=e.node.getAttribute("action"),i.input=pw.node.serialize(e.node)}else{var a={};a[o.scope]=n,i.input=a}var d=this;window.socket.send(i,function(e){if(302===e.status){var o=e.headers.Location;o!=window.location.pathname||window.context&&"default"===window.context.name?history.pushState({uri:o},o,o):history.pushState({uri:o},o,o)}else{if(400===e.status)return void pw.component.broadcast("response:received",{response:e});d.state.rollback()}pw.component.broadcast("response:received",{response:e}),"false"!==t.revert&&d.revert()})}});
@@ -80,6 +80,9 @@ function handleState(state, direction) {
80
80
  return;
81
81
  }
82
82
 
83
+ uri = uri.replace(document.location.origin, '');
84
+ pw.component.broadcast('navigator:change', { uri: uri });
85
+
83
86
  if (state.context) {
84
87
  state.r_uri = document.location.pathname + '#:' + state.context + '/' + uri;
85
88
 
@@ -135,16 +138,17 @@ function handleState(state, direction) {
135
138
  var body = payload.body[0];
136
139
 
137
140
  if (body.match(/<title>/)) {
138
- document.title = body.split(/<title>/)[1].split('</title>')[0];
141
+ var title = body.split(/<title>/)[1].split('</title>')[0];
142
+ document.querySelector('title').innerHTML = title;
139
143
  }
140
144
 
141
- if (body.match(/<body [^>]*>/)) {
142
- document.body.innerHTML = body.split(/<body [^>]*>/)[1].split('</body>')[0];
143
- } else {
144
- document.body.innerHTML = body;
145
- }
145
+ var doc = document.documentElement.cloneNode();
146
+ doc.innerHTML = body;
146
147
 
148
+ document.body.innerHTML = doc.querySelector('body').innerHTML;
147
149
  pw.component.findAndInit(document.querySelectorAll('body')[0]);
150
+
151
+ document.body.scrollTop = document.documentElement.scrollTop = 0;
148
152
  }
149
153
  });
150
154
  }
@@ -1 +1 @@
1
- function boot(){if(!window.socket)return void setTimeout(boot,100);if(window.location.hash){var t=window.location.hash.split("#:")[1].split("/"),n=t.shift(),o=t.join("/");pw.component.broadcast(n+":navigator:boot",o)}}function handleState(t,n){var o=t.uri||t.url;if(!window.socket)return void(document.location=o);if(t.context)t.r_uri=document.location.pathname+"#:"+t.context+"/"+o,window.context={_state:t,name:t.context,uri:t.r_uri,container:t.container,partial:t.partial};else if(t.r_uri=o,"default"!==window.context.name){if("back"===n)return pw.component.broadcast(window.context.name+":navigator:exit"),void(window.context={name:"default",uri:t.uri});t.r_uri=document.location.pathname+"#:"+window.context.name+"/"+o,t.context=window.context.name,t.container=window.context.container,t.partial=window.context.partial}var i={uri:o,action:"call-route",method:"get"};t.container&&(i.container=t.container),t.partial&&(i.partial=t.partial),window.socket.send(i,function(n){if(t.context)pw.component.broadcast(t.context+":navigator:enter",n);else{var o=n.body[0];o.match(/<title>/)&&(document.title=o.split(/<title>/)[1].split("</title>")[0]),o.match(/<body [^>]*>/)?document.body.innerHTML=o.split(/<body [^>]*>/)[1].split("</body>")[0]:document.body.innerHTML=o,pw.component.findAndInit(document.querySelectorAll("body")[0])}})}!function(t){if(pw.init.register(boot),t){var n=!1,o=t.pushState;t.pushState=function(i,e,a){return n=!0,"function"==typeof t.onpushstate&&t.onpushstate({state:i}),a==window.location.pathname?(pw.component.broadcast(window.context.name+":navigator:exit"),window.context={_state:i,name:"default",uri:window.location.href},i.r_uri=a):handleState(i,"forward"),o.apply(t,[i,e,i.r_uri])},window.onpopstate=function(t){if(n){var o=t.state;o||(o={}),o.uri||(o.uri=window.context.uri),handleState(o,"back")}}}}(window.history),window.context={name:"default",uri:window.location.href};
1
+ function boot(){if(!window.socket)return void setTimeout(boot,100);if(window.location.hash){var t=window.location.hash.split("#:")[1].split("/"),n=t.shift(),o=t.join("/");pw.component.broadcast(n+":navigator:boot",o)}}function handleState(t,n){var o=t.uri||t.url;if(!window.socket)return void(document.location=o);if(o=o.replace(document.location.origin,""),pw.component.broadcast("navigator:change",{uri:o}),t.context)t.r_uri=document.location.pathname+"#:"+t.context+"/"+o,window.context={_state:t,name:t.context,uri:t.r_uri,container:t.container,partial:t.partial};else if(t.r_uri=o,"default"!==window.context.name){if("back"===n)return pw.component.broadcast(window.context.name+":navigator:exit"),void(window.context={name:"default",uri:t.uri});t.r_uri=document.location.pathname+"#:"+window.context.name+"/"+o,t.context=window.context.name,t.container=window.context.container,t.partial=window.context.partial}var e={uri:o,action:"call-route",method:"get"};t.container&&(e.container=t.container),t.partial&&(e.partial=t.partial),window.socket.send(e,function(n){if(t.context)pw.component.broadcast(t.context+":navigator:enter",n);else{var o=n.body[0];if(o.match(/<title>/)){var e=o.split(/<title>/)[1].split("</title>")[0];document.querySelector("title").innerHTML=e}var i=document.documentElement.cloneNode();i.innerHTML=o,document.body.innerHTML=i.querySelector("body").innerHTML,pw.component.findAndInit(document.querySelectorAll("body")[0]),document.body.scrollTop=document.documentElement.scrollTop=0}})}!function(t){if(pw.init.register(boot),t){var n=!1,o=t.pushState;t.pushState=function(e,i,a){return n=!0,"function"==typeof t.onpushstate&&t.onpushstate({state:e}),a==window.location.pathname?(pw.component.broadcast(window.context.name+":navigator:exit"),window.context={_state:e,name:"default",uri:window.location.href},e.r_uri=a):handleState(e,"forward"),o.apply(t,[e,i,e.r_uri])},window.onpopstate=function(t){if(n){var o=t.state;o||(o={}),o.uri||(o.uri=window.context.uri),handleState(o,"back")}}}}(window.history),window.context={name:"default",uri:window.location.href};
@@ -1,5 +1,5 @@
1
1
  var pw = {
2
- version: '0.1.1'
2
+ version: '0.2.4'
3
3
  };
4
4
 
5
5
  (function() {
@@ -142,7 +142,7 @@ pw.node = {
142
142
  }
143
143
 
144
144
  var next = node.parentNode;
145
- if (next !== document) {
145
+ if (next && next !== document) {
146
146
  return pw.node.inForm(next);
147
147
  }
148
148
  },
@@ -154,7 +154,7 @@ pw.node = {
154
154
  }
155
155
 
156
156
  var next = node.parentNode;
157
- if (next !== document) {
157
+ if (next && next !== document) {
158
158
  return pw.node.component(next);
159
159
  }
160
160
  },
@@ -166,7 +166,7 @@ pw.node = {
166
166
  }
167
167
 
168
168
  var next = node.parentNode;
169
- if (next !== document) {
169
+ if (next && next !== document) {
170
170
  return pw.node.scope(next);
171
171
  }
172
172
  },
@@ -178,7 +178,7 @@ pw.node = {
178
178
  }
179
179
 
180
180
  var next = node.parentNode;
181
- if (next !== document) {
181
+ if (next && next !== document) {
182
182
  return pw.node.scopeName(next);
183
183
  }
184
184
  },
@@ -190,7 +190,7 @@ pw.node = {
190
190
  }
191
191
 
192
192
  var next = node.parentNode;
193
- if (next !== document) {
193
+ if (next && next !== document) {
194
194
  return pw.node.prop(next);
195
195
  }
196
196
  },
@@ -202,7 +202,7 @@ pw.node = {
202
202
  }
203
203
 
204
204
  var next = node.parentNode;
205
- if (next !== document) {
205
+ if (next && next !== document) {
206
206
  return pw.node.propName(next);
207
207
  }
208
208
  },
@@ -215,11 +215,11 @@ pw.node = {
215
215
  },
216
216
 
217
217
  // creates a context in which view manipulations can occur
218
- with: function(node, cb) {
218
+ invoke: function(node, cb) {
219
219
  cb.call(node);
220
220
  },
221
221
 
222
- for: function(node, data, cb) {
222
+ invokeWithData: function(node, data, cb) {
223
223
  if (pw.node.isNodeList(node)) {
224
224
  node = pw.node.toA(node);
225
225
  }
@@ -257,14 +257,14 @@ pw.node = {
257
257
  },
258
258
 
259
259
  repeat: function(node, data, cb) {
260
- pw.node.for(pw.node.match(node, data), data, cb);
260
+ pw.node.invokeWithData(pw.node.match(node, data), data, cb);
261
261
  },
262
262
 
263
263
  // binds an object to a node
264
264
  bind: function (data, node, cb) {
265
265
  var scope = pw.node.findBindings(node)[0];
266
266
 
267
- pw.node.for(node, data, function(dm) {
267
+ pw.node.invokeWithData(node, data, function(dm) {
268
268
  if (!dm) {
269
269
  return;
270
270
  }
@@ -389,7 +389,9 @@ pw.node = {
389
389
  } else if (node.tagName === 'TEXTAREA' || pw.node.isSelfClosingTag(node)) {
390
390
  node.value = value;
391
391
  } else {
392
- node.innerHTML = value;
392
+ if (value) {
393
+ node.innerHTML = value;
394
+ }
393
395
  }
394
396
  },
395
397
 
@@ -725,6 +727,12 @@ pw_State.prototype = {
725
727
 
726
728
  // gets the current represented state from the node and diffs it with the current state
727
729
  diffNode: function (node) {
730
+ if (node.hasAttribute('data-ui')) {
731
+ return {
732
+ '__nested': pw.state.build(pw.node.significant(node))
733
+ };
734
+ }
735
+
728
736
  return pw.state.build(pw.node.significant(pw.node.scope(node)))[0];
729
737
  },
730
738
 
@@ -758,7 +766,7 @@ pw_State.prototype = {
758
766
  this.snapshots.push(copy);
759
767
  },
760
768
 
761
- delete: function (state) {
769
+ remove: function (state) {
762
770
  var copy = this.copy();
763
771
  var match = copy.find(function (s) {
764
772
  return s.id === state.id;
@@ -793,8 +801,15 @@ pw.view = {
793
801
  },
794
802
 
795
803
  fromStr: function (str) {
796
- var e = document.createElement("div");
804
+ var nodeType = 'div';
805
+
806
+ if (str.match(/^<tr/) || str.match(/^<tbody/)) {
807
+ nodeType = 'table';
808
+ }
809
+
810
+ var e = document.createElement(nodeType);
797
811
  e.innerHTML = str;
812
+
798
813
  return pw.view.init(e.childNodes[0]);
799
814
  }
800
815
  };
@@ -839,16 +854,16 @@ pw_View.prototype = {
839
854
  return pw.attrs.init(this);
840
855
  },
841
856
 
842
- with: function (cb) {
843
- pw.node.with(this.node, cb);
857
+ invoke: function (cb) {
858
+ pw.node.invoke(this.node, cb);
844
859
  },
845
860
 
846
861
  match: function (data) {
847
862
  pw.node.match(this.node, data);
848
863
  },
849
864
 
850
- for: function (data, cb) {
851
- pw.node.for(this.node, data, cb);
865
+ invokeWithData: function (data, cb) {
866
+ pw.node.invokeWithData(this.node, data, cb);
852
867
  },
853
868
 
854
869
  repeat: function (data, cb) {
@@ -861,6 +876,41 @@ pw_View.prototype = {
861
876
 
862
877
  apply: function (data, cb) {
863
878
  pw.node.apply(data, this.node, cb);
879
+ },
880
+
881
+ use: function (version, cb) {
882
+ var self = this;
883
+
884
+ if (this.node.getAttribute('data-version') != version) {
885
+ this.node.setAttribute('data-version', version);
886
+
887
+ var lookup = {
888
+ scope: this.node.getAttribute('data-scope'),
889
+ version: version
890
+ };
891
+
892
+ window.socket.fetchView(lookup, function (view) {
893
+ view.node.setAttribute('data-channel', self.node.getAttribute('data-channel'));
894
+ pw.node.replace(self.node, view.node);
895
+ self.node = view.node;
896
+ cb();
897
+ });
898
+ } else {
899
+ cb();
900
+ }
901
+ },
902
+
903
+ setEndpoint: function (endpoint) {
904
+ this.endpoint = endpoint;
905
+ return this;
906
+ },
907
+
908
+ first: function () {
909
+ return this;
910
+ },
911
+
912
+ length: function () {
913
+ return 1;
864
914
  }
865
915
  };
866
916
 
@@ -875,7 +925,7 @@ pw_View.prototype = {
875
925
  });
876
926
 
877
927
  // pass through functions without view
878
- ['remove', 'clear', 'versionNode'].forEach(function (method) {
928
+ ['remove', 'clear', 'versionName'].forEach(function (method) {
879
929
  pw_View.prototype[method] = function () {
880
930
  return pw.node[method](this.node);
881
931
  };
@@ -908,7 +958,7 @@ pw.collection = {
908
958
  var pw_Collection = function (views, parent, scope) {
909
959
  this.views = views;
910
960
  this.parent = parent;
911
- this.scope = scope;
961
+ this._scope = scope;
912
962
  };
913
963
 
914
964
  pw_Collection.prototype = {
@@ -1007,11 +1057,11 @@ pw_Collection.prototype = {
1007
1057
  return pw.collection.init(prependedViews);
1008
1058
  },
1009
1059
 
1010
- with: function (cb) {
1011
- pw.node.with(this.views, cb);
1060
+ invoke: function (cb) {
1061
+ pw.node.invoke(this.views, cb);
1012
1062
  },
1013
1063
 
1014
- for: function(data, fn) {
1064
+ invokeWithData: function(data, fn) {
1015
1065
  data = Array.ensure(data);
1016
1066
 
1017
1067
  this.views.forEach(function (view, i) {
@@ -1037,12 +1087,13 @@ pw_Collection.prototype = {
1037
1087
  this.views.slice(0).forEach(function (view) {
1038
1088
  var id = view.node.getAttribute('data-id');
1039
1089
 
1040
- if (!id) {
1041
- return;
1042
- }
1043
-
1044
- if (!data.find(function (datum) { return datum.id.toString() === id })) {
1090
+ if (!id && data[0].id) {
1045
1091
  this.removeView(view);
1092
+ return;
1093
+ } else if (id) {
1094
+ if (!data.find(function (datum) { return datum.id && datum.id.toString() === id })) {
1095
+ this.removeView(view);
1096
+ }
1046
1097
  }
1047
1098
  }, this);
1048
1099
 
@@ -1083,12 +1134,12 @@ pw_Collection.prototype = {
1083
1134
 
1084
1135
  repeat: function (data, fn) {
1085
1136
  this.match(data, function () {
1086
- this.for(data, fn);
1137
+ this.invokeWithData(data, fn);
1087
1138
  });
1088
1139
  },
1089
1140
 
1090
1141
  bind: function (data, fn) {
1091
- this.for(data, function(datum) {
1142
+ this.invokeWithData(data, function(datum) {
1092
1143
  this.bind(datum);
1093
1144
 
1094
1145
  if(!(typeof fn === 'undefined')) {
@@ -1113,7 +1164,14 @@ pw_Collection.prototype = {
1113
1164
  });
1114
1165
  },
1115
1166
 
1116
- endpoint: function (endpoint) {
1167
+ version: function (data, fn) {
1168
+ var self = this;
1169
+ this.match(data, function () {
1170
+ this.invokeWithData(data, fn);
1171
+ });
1172
+ },
1173
+
1174
+ setEndpoint: function (endpoint) {
1117
1175
  this.endpoint = endpoint;
1118
1176
  return this;
1119
1177
  }
@@ -1320,11 +1378,10 @@ pw_Component.prototype = {
1320
1378
 
1321
1379
  // make it mutable
1322
1380
  var mutableCb = function (evt) {
1323
- evt.preventDefault();
1324
-
1325
1381
  var scope = pw.node.scope(evt.target);
1326
1382
 
1327
1383
  if (scope) {
1384
+ evt.preventDefault();
1328
1385
  self.mutated(scope);
1329
1386
  }
1330
1387
  };
@@ -1351,6 +1408,33 @@ pw_Component.prototype = {
1351
1408
  pw.component.deregisterForBroadcast(channel, this);
1352
1409
  },
1353
1410
 
1411
+ // Bubbles an event up to a parent component. Intended to be used
1412
+ // as an alternative to `broadcast` in cases where child components
1413
+ // have an impact on their parents.
1414
+ bubble: function (channel, payload) {
1415
+ var parentComponent = pw.node.component(this.node.parentNode);
1416
+
1417
+ (channelBroadcasts[channel] || []).forEach(function (cbTuple) {
1418
+ if (cbTuple[1].node == parentComponent) {
1419
+ cbTuple[0].call(cbTuple[1], payload);
1420
+ }
1421
+ });
1422
+ },
1423
+
1424
+ // Trickles an event down to child components. Intended to be used
1425
+ // as an alternative to `broadcast` in cases where parent components
1426
+ // have an impact on their children.
1427
+ trickle: function (channel, payload) {
1428
+ var channels = (channelBroadcasts[channel] || []);
1429
+ pw.node.toA(this.node.getElementsByTagName('*')).forEach(function (node) {
1430
+ channels.forEach(function (cbTuple) {
1431
+ if (cbTuple[1].node == node) {
1432
+ cbTuple[0].call(cbTuple[1], payload);
1433
+ }
1434
+ });
1435
+ })
1436
+ },
1437
+
1354
1438
  //TODO this is pretty similary to processing instructions
1355
1439
  // for views in that we also have to handle the empty case
1356
1440
  //
@@ -1407,7 +1491,7 @@ pw_Component.prototype = {
1407
1491
  }
1408
1492
 
1409
1493
  if (state.length > 0) {
1410
- this.view.scope(state[0].scope).endpoint(this.endpoint || this).apply(state);
1494
+ this.view.scope(state[0].scope).setEndpoint(this.endpoint || this).apply(state);
1411
1495
  } else {
1412
1496
  pw.node.breadthFirst(this.view.node, function () {
1413
1497
  if (this.hasAttribute('data-scope')) {
@@ -1435,8 +1519,8 @@ pw_Component.prototype = {
1435
1519
  }
1436
1520
  },
1437
1521
 
1438
- delete: function (data) {
1439
- this.state.delete(data);
1522
+ remove: function (data) {
1523
+ this.state.remove(data);
1440
1524
  this.transform(this.state.current());
1441
1525
  },
1442
1526
 
@@ -1470,6 +1554,7 @@ pw.init.register(function () {
1470
1554
  pw.socket.init({
1471
1555
  cb: function (socket) {
1472
1556
  window.socket = socket;
1557
+ pw.component.broadcast('socket:available');
1473
1558
  }
1474
1559
  });
1475
1560
  });
@@ -1671,7 +1756,9 @@ pw.instruct = {
1671
1756
  });
1672
1757
  },
1673
1758
 
1674
- // TODO: make this smart and cache results
1759
+ // TODO: make this smart and cache results, invalidating
1760
+ // if the websocket connection reconnects (since that means
1761
+ // the server probably restarted)
1675
1762
  template: function (view, cb) {
1676
1763
  var lookup = {};
1677
1764
 
@@ -1696,23 +1783,46 @@ pw.instruct = {
1696
1783
  });
1697
1784
  },
1698
1785
 
1699
- perform: function (collection, instructions) {
1786
+ perform: function (collection, instructions, cb) {
1700
1787
  var self = this;
1788
+ instructions = instructions || [];
1701
1789
 
1702
- (instructions || []).forEach(function (instruction, i) {
1790
+ function instruct (subject, instruction) {
1703
1791
  var method = instruction[0];
1704
1792
  var value = instruction[1];
1705
1793
  var nested = instruction[2];
1706
1794
 
1795
+ // remap instructions to the ring name
1796
+ if (method === 'with') {
1797
+ method = 'invoke';
1798
+ }
1799
+
1800
+ if (method === 'for') {
1801
+ method = 'invokeWithData';
1802
+ }
1803
+
1707
1804
  if (collection[method]) {
1708
- if (method == 'with' || method == 'for' || method == 'bind' || method == 'repeat' || method == 'apply') {
1709
- collection.endpoint(self)[method].call(collection, value, function (datum) {
1710
- pw.instruct.perform(this, nested[value.indexOf(datum)]);
1805
+ if (method == 'invoke' || method == 'invokeWithData' || method == 'bind' || method == 'repeat' || method == 'apply' || method == 'version') {
1806
+ var cbLength = collection.length();
1807
+ var cbCount = 0;
1808
+ var nestedCb = function () {
1809
+ cbCount++;
1810
+
1811
+ if (cbCount == cbLength) {
1812
+ next();
1813
+ }
1814
+ }
1815
+ collection.setEndpoint(self)[method].call(collection, value, function (datum) {
1816
+ pw.instruct.perform(this, nested[value.indexOf(datum)], nestedCb);
1711
1817
  });
1712
1818
  return;
1713
1819
  } else if (method == 'attrs') {
1714
1820
  self.performAttr(collection.attrs(), nested);
1715
1821
  return;
1822
+ } else if (method == 'use') {
1823
+ collection.setEndpoint(self);
1824
+ collection.use(value, next);
1825
+ return;
1716
1826
  } else {
1717
1827
  var mutatedViews = collection[method].call(collection, value);
1718
1828
  }
@@ -1722,13 +1832,33 @@ pw.instruct = {
1722
1832
  }
1723
1833
 
1724
1834
  if (nested instanceof Array) {
1725
- pw.instruct.perform(mutatedViews, nested);
1835
+ pw.instruct.perform(mutatedViews, nested, next);
1836
+ return;
1726
1837
  } else if (mutatedViews) {
1727
1838
  collection = mutatedViews;
1728
1839
  }
1729
- });
1730
1840
 
1731
- pw.component.findAndInit(collection.node);
1841
+ next();
1842
+ };
1843
+
1844
+ var i = 0;
1845
+ function next() {
1846
+ if (i < instructions.length) {
1847
+ instruct(collection, instructions[i++]);
1848
+ } else {
1849
+ done();
1850
+ }
1851
+ };
1852
+
1853
+ function done() {
1854
+ if (cb) {
1855
+ cb();
1856
+ } else {
1857
+ pw.component.findAndInit(collection.node);
1858
+ }
1859
+ };
1860
+
1861
+ next();
1732
1862
  },
1733
1863
 
1734
1864
  performAttr: function (context, attrInstructions) {