Paul: Check-in [582ba26718]

Online event coordination and survey application

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Continue to work on survey creation
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256:582ba2671880e663b7dd89adfcbe62d209af877916842c189ccb52554dcf8302
User & Date: milouse 2018-10-22 19:12:49
Context
2018-10-23
07:19
chore: Upgrade npm check-in: c47e7ac78b user: milouse tags: trunk
2018-10-22
19:12
Continue to work on survey creation check-in: 582ba26718 user: milouse tags: trunk
08:09
feat: Add missing project files check-in: d929567a47 user: milouse tags: trunk
Changes

Changes to .fossil-settings/ignore-glob.

1
2
3
4
5
6

.ruby-gemset
.bundle
tmp/logs/application.log
tmp/pids/sinatra.pid
node_modules
public/paul.js*







>
1
2
3
4
5
6
7
.ruby-gemset
.bundle
tmp/logs/application.log
tmp/pids/sinatra.pid
node_modules
public/paul.js*
polls/*.yml

Changes to assets/coffee/main.coffee.

1
2
3
4
5
6
7
8
9
10



11
12

13


14
15
16

17
18
19
20
21




22
23





24
25
26
27

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class YesNoPoll
  constructor: (@create_form) ->
    @template = @create_form.querySelector \
      '#survey_fields>div.form_group'
    return null unless @template?
    nb = document.getElementById('new_props')
    return null unless nb?
    @proposal_count = 1
    nb.addEventListener 'click', (event) ->
      currentYesNoPoll.insert_template()




  remove_template: (elem) ->

    console.log '>> elem', elem.parentElement



  insert_template: ->
    addProps = document.querySelector '#survey_fields>div.add_props'

    return false unless addProps?
    newProp = @template.cloneNode(true)
    prop = @proposal_count + 1
    label = newProp.querySelector('label')
    label.textContent = "Proposal #{prop}:"




    label.setAttribute('for', "props#{prop}field")
    input = newProp.querySelector('input')





    input.setAttribute('id', "props#{prop}field")
    input.value = ''
    input.required = false
    addProps.parentElement.insertBefore newProp, addProps

    remLink = document.createElement('input')
    remLink.type = 'button'
    remLink.value = '×'
    remLink.title = 'Remove proposal'
    remLink.addEventListener 'click', (event) ->
      event.preventDefault()
      event.stopPropagation()
      currentYesNoPoll.remove_template(event.target)
    newProp.appendChild remLink
    @proposal_count = prop

currentYesNoPoll = null
document.addEventListener 'DOMContentLoaded', ->
  cp = document.getElementById('create_poll')
  return unless cp?
  surveyType = document.querySelector(
    '#create_poll input[name=form_type]')
  return unless surveyType?
  if surveyType.value == 'yesno'
    currentYesNoPoll = new YesNoPoll(cp)
|
|
|
<
|


<

|
>
>
>


>
|
>
>


<
>
|
<
<
<
<
>
>
>
>

<
>
>
>
>
>

<
<
<
>







|

|

|

|

|
<
<
<
<
1
2
3

4
5
6

7
8
9
10
11
12
13
14
15
16
17
18
19

20
21




22
23
24
25
26

27
28
29
30
31
32



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49




class SurveyManager
  constructor: (@poll_form) ->
    pt = @poll_form.elements.poll_type

    return null unless pt?
    nb = document.getElementById('new_props')
    return null unless nb?

    nb.addEventListener 'click', (event) ->
      currentSurveyManager.insert_template()
    @poll_type = pt.value
    pt.addEventListener 'change', (event) ->
      currentSurveyManager.poll_type = event.target.value

  remove_template: (elem) ->
    allProps = document.getElementById 'survey_fields'
    tpl = elem.parentElement
    return false if !allProps? or !tpl?
    allProps.removeChild(tpl)

  insert_template: ->

    allProps = document.getElementById 'survey_fields'
    return false unless allProps




    prop = String(Math.floor(Math.random() * 1000000))
    newProp = document.createElement('LI')
    newProp.className = 'form_group'
    label = document.createElement('LABEL')
    label.setAttribute('for', "props#{prop}field")

    label.textContent = 'Proposal '
    newProp.appendChild label
    input = document.createElement('INPUT')
    input.setAttribute('type', 'text')
    input.setAttribute('name', 'proposals[]')
    input.setAttribute('id', "props#{prop}field")



    newProp.appendChild input
    remLink = document.createElement('input')
    remLink.type = 'button'
    remLink.value = '×'
    remLink.title = 'Remove proposal'
    remLink.addEventListener 'click', (event) ->
      event.preventDefault()
      event.stopPropagation()
      currentSurveyManager.remove_template(event.target)
    newProp.appendChild remLink
    allProps.appendChild(newProp)

window['currentSurveyManager'] = null
document.addEventListener 'DOMContentLoaded', ->
  cp = document.getElementById('poll_form')
  return unless cp?
  window.currentSurveyManager = new SurveyManager(cp)




Added lib/survey.rb.

























































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# frozen_string_literal: true

require 'yaml'
require 'securerandom'

# String helpers
class String
  def to_bool
    return true if self =~ /(true|yes|1)$/i
    return false if self == '' || self =~ /(false|no|0)$/i
    raise ArgumentError, "invalid value for Boolean: \"#{self}\""
  end
end

# Survey model
class Survey
  attr_reader :id
  attr_reader :is_new
  attr_reader :data

  def initialize(opts = {})
    @is_new = false
    @id = nil
    if opts.is_a? String
      @id = opts
      opts = {}
    elsif !opts.is_a? Hash
      raise TypeError, 'Survey initialize only accept String or Hash'
    elsif opts.has_key? 'id'
      @id = opts.delete('id')
    end
    init_id_and_poll_path
    load_data_from_poll_path
    extract_data(opts) unless opts.empty?
  end

  def save
    IO.write @poll_path, @data.to_yaml
  end

  def to_s
    @id
  end

  private

  def init_id_and_poll_path
    if @id.nil? || @id == '' || @id == 'new'
      @is_new = true
      @id = SecureRandom.hex(25)
    end
    @poll_path = File.join('polls', "#{@id}.yml")
  end

  def load_data_from_poll_path
    if @is_new
      @data = { id: @id, maybe: false }
    elsif File.exist? @poll_path
      @data = YAML.load_file(@poll_path).freeze
    else
      raise NameError, 'Survey not found'
    end
  end

  def extract_data(opts)
    @data[:title] = opts['title'] if opts.has_key? 'title'
    @data[:author] = opts['author'] if opts.has_key? 'author'
    @data[:poll_type] = opts['poll_type'] || 'yesno'
    @data[:maybe] = opts['maybe'].to_s.to_bool if opts.has_key? 'maybe'
    return unless opts.has_key? 'proposals'
    @data[:proposals] ||= []
    opts['proposals'].each do |p|
      @data[:proposals] << p
    end
  end
end

Changes to paul.rb.

1
2
3
4
5





6
7
8
9
10
11
12
..
26
27
28
29
30
31
32









33
34
35
36
37

38
39
40
41
42
43






44
45
46
47
48
49




50
51
52
53
54
55
56
57
# frozen_string_literal: true

require 'sinatra/base'
require 'logger'
require 'slim/smart'






# Main Sinatra application
class Paul < Sinatra::Base
  configure :production, :development do
    enable :logging
    app_logger = Logger.new("#{root}/tmp/logs/application.log")
    app_logger.datetime_format = '%Y-%m-%d %H:%M:%S'
................................................................................
    def logger
      if settings.respond_to?(:logger)
        settings.logger
      else
        request.logger
      end
    end









  end

  post '/create' do
    logger.info ">>>> params #{params.inspect}"
    @title = 'Survey created'

    slim :create
  end

  get '/date' do
    @title = 'Create a date poll'
    @form_type = :date






    slim :create
  end

  get '/yesno' do
    @title = 'Create a yes/no/maybe survey'
    @form_type = :yesno




    slim :create
  end

  get '/' do
    @title = 'Paul'
    slim :index
  end






>
>
>
>
>







 







>
>
>
>
>
>
>
>
>



|
|
>
|


<
|
|
>
>
>
>
>
>
|


|
|
|
>
>
>
>
|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
..
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# frozen_string_literal: true

require 'sinatra/base'
require 'logger'
require 'slim/smart'
require './lib/survey'

# To be included:
# Digest https://ruby-doc.org/stdlib-2.4.4/libdoc/digest/rdoc/Digest.html
# SecureRandom https://ruby-doc.org/stdlib-2.4.4/libdoc/securerandom/rdoc/SecureRandom.html

# Main Sinatra application
class Paul < Sinatra::Base
  configure :production, :development do
    enable :logging
    app_logger = Logger.new("#{root}/tmp/logs/application.log")
    app_logger.datetime_format = '%Y-%m-%d %H:%M:%S'
................................................................................
    def logger
      if settings.respond_to?(:logger)
        settings.logger
      else
        request.logger
      end
    end

    def share_url(survey)
      uri = "#{request.scheme}://#{request.host}"
      unless [443, 80].include?(request.port)
        uri = "#{uri}:#{request.port}"
      end
      uri << "#{uri}#{request.script_name}" if request.script_name != ''
      "#{uri}/p/#{survey.id}"
    end
  end

  post '/create' do
    @poll = Survey.new(params)
    @poll.save
    @title = @poll.data[:title]
    slim :poll
  end


  get '/p/:poll_id' do
    begin
      @poll = Survey.new(params[:poll_id])
    rescue NameError => e
      logger.error e
      raise Sinatra::NotFound
    end
    @title = @poll.data[:title]
    slim :poll
  end

  get '/new' do
    @title = 'Create a new poll'
    @poll_type = :yesno
    if params.has_key?(:poll_type) &&
       ['yesno', 'date'].include?(params[:poll_type])
      @poll_type = params[:poll_type]
    end
    slim :form
  end

  get '/' do
    @title = 'Paul'
    slim :index
  end

Changes to public/style.css.

1
2
3
4
5
6
7













body {
    font-family: sans;
}

fieldset {
    border: 0px;
}




















>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
body {
    font-family: sans;
}

fieldset {
    border: 0px;
}

table, th, tr, td {
    border-collapse: collapse;
    border: 1px solid #000000;
}

td, th {
    padding: .5em 1em;
}

input.share_url {
    width: 600px;
}

Added views/form.slim.























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
form#poll_form method='post' action='/create'
  fieldset
    div.form_group
      label> for='titlefield' Title
      input#titlefield type='text' name='title' required=true
    div.form_group
      label> for='authorfield' Display name
      input#authorfield type='text' name='author' required=true
    div.form_group
      label> for='formtypefield' This is a
      select#formtypefield name='poll_type' required=true
        option value='yesno' selected=(@poll_type == 'yesno') Yes/No survey
        option value='date' selected=(@poll_type == 'date') Date poll
    div.form_group
      label
        input type='checkbox' name='maybe' value='yes'
        | Add a `maybe` option
  fieldset
    ol#survey_fields
      li.form_group
        label> for='props1field' Proposal
        input#props1field type='text' name='proposals[]' required=true
    div.add_props
      input#new_props type='button' value='Add a new proposal'
  fieldset
    div.form_group.submit
      input type='submit'

Changes to views/index.slim.


1


a href='yesno' Create a yes/no/maybe survey

>
|
>
1
2
3
ul
  li: a href='new?poll_type=yesno' Create a yes/no/maybe survey
  li: a href='new?poll_type=date' Create a date poll

Added views/poll.slim.





















>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
p
  by #{@poll.data[:author]}

p
  input.share_url type='text' readonly=true value=share_url(@poll)

table
  tr
    - for prop in @poll.data[:proposals]
      th = prop