die Qual der Wahl mit Selenium 1

Posted by fwoeck
on Tuesday, May 26

Wie bringe ich meinem Integrationstest – also hier letztlich Selenium – bei, mehrere Optionen eines Multiselect-Feldes auszuwählen? Die vom SeleniumDriver angebotene Methode select sieht so aus:

select(selectLocator,optionLocator)

wobei der optionLocator soweit ich es sehe nur eine(1) Option zulässt. Das mehrmalige Aufrufen nacheinander klappt leider auch nicht, weil die vorher angewählte Option beim neuen Schritt wieder deaktiviert wird.

Dazu ist mir nur eingefallen, den Wert mit JavaScript zu setzen. Hier benutze ich jQuery, weil es in der Website eh benutzt wird. Mit “run_script” lassen sich Skripte unter dem Driver ausführen. In einer Cucumber-Stepsdefinition sieht das dann so aus:

Given /^I choose "([^\"]*)" and "([^\"]*)" from "([^\"]*)"$/ do |opt1, opt2, field|
  selenium.run_script("$('##{field}').val(['#{opt1}','#{opt2}'])")
end

Der val-Methode von jQuery kann man ein Wertearray übergeben.

Weblinks

  1. selenium.rubyforge.org/rdoc/classes/Selenium
  2. jquery-select-elements-tips-and-tricks

Die Google-Analytics API spricht mit dir 0

Posted by fwoeck
on Saturday, May 02

Kurz nach der Veröffentlichung der neuen Google-Analytics-API gibt es jetzt das gem garb, welches die Kommunikation damit erheblich vereinfacht. In diesem Beispiel frage ich die Werte visits, pageviews, etc. für den letzten Monat ab und übergebe sie dem jQuery-Plugin flot, das diese Daten auf eine recht hübsche interaktive Weise darstellt:

Hier folgen die Ergänzungen zu einer leeren Rails-2.3.2-Instanz. Das gesamte Projekt habe ich zum Clonen und Spielen auf Github eingestellt (s.u.).

Die Umgebung

Das gem wird aufgerufen (> 0.2), aus einem YAML-File werden die Accountinformationen eingelesen und eine Session mit Google etabliert.

In config/environment.rb:

1
2
3
4
5
6
7
8
9
10
...
Rails::Initializer.run do |config|
...
  config.gem "vigetlabs-garb", :lib => "garb"
...
end

google_path = File.join(RAILS_ROOT,"config","google.yml")
@@google_config = YAML.load(File.read(google_path))[RAILS_ENV]
Garb::Session.login(@@google_config['login'], @@google_config['password'])

Anmeldedaten

Das File config/google.yml enthält die Domaindaten:

1
2
3
4
5
6
7
8
9
development:
  domain: rnotes.bm.net
  login: me@bm.net
  password: p4ssw0rd

production:
  domain: rnotes.bm.net
  login: me@bm.net
  password: p4ssw0rd

Controller-Code

Wenn der Welcome-Index aufgerufen wird, wird die Datenabfrage im Controller assembliert und aufgerufen. Einer sauberen MVC-Kultur folgend, sollte man dies in einem richtigen Projekt vielleicht in ein Modell geben.

app/controllers/welcome_controller.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class WelcomeController < ApplicationController

  def index
    profile = Garb::Profile.all.select {|p| p.title == 
                              @@google_config['domain']}.first
    report = Garb::Report.new(profile, :start_date => 
                              4.weeks.ago, :end_date => Time.now )

    report.metrics :pageviews, :visits, :visitors, :new_visits
    report.dimensions :date
    report.sort :date
    result = report.results

    @visitors = result.map(&:visitors)
    @visits = result.map(&:visits)
    @newvisits = result.map(&:new_visits)
    @pageviews = result.map(&:pageviews)
    @dates = result.map {|d| d.date.to_time.to_i * 1000 }
    @domain = @@google_config['domain']
  end

end

Hier ist bemerkenswert, wie die Datumsformate “CCYYMMDD”, die von Google als String kommen in Javascript-Timestamps umgerechnet werden, wie flot sie erwartet.

Die Diagrammdarstellung

Im Index werden die Ergebnisse ein ein Javascript eingefügt, das letztlich flot benutzt. Weitere Beispiele mit verschiedenen Optionen findet man auf der Website des Plugins.

Das app/views/welcome/index.html.erb-File:

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
<h1><%= @domain -%></h1>

<p><br /></p>

<div id="placeholder" style="width:600px;height:300px"></div>
<div id="overview" style="margin-left:200px;margin-top:20px;
                                           width:400px;height:50px"></div>

<script id="source" language="javascript" type="text/javascript">
$(function () {
    var d1 = [<%= i = -1; @visitors.map {|v| i += 1; 
                        "[#{@dates[i]}, #{v}]" }.join(", ") -%>];
    var d2 = [<%= i = -1; @visits.map {|v| i += 1; 
                        "[#{@dates[i]}, #{v}]" }.join(", ") -%>];
    var d3 = [<%= i = -1; @pageviews.map {|v| i += 1; 
                        "[#{@dates[i]}, #{v}]" }.join(", ") -%>];
    var d4 = [<%= i = -1; @newvisits.map {|v| i += 1; 
                        "[#{@dates[i]}, #{v}]" }.join(", ") -%>];
    var d = [ { label: 'Visitors', data: d1 }, { label: 'Visits', data: d2 }, 
        { label: 'Pageviews', data: d3 },  { label: 'New visits', data: d4 } ];

    // helper for returning the weekends in a period
    function weekendAreas(axes) {
        var markings = [];
        var d = new Date(axes.xaxis.min);
        // go to the first Saturday
        d.setUTCDate(d.getUTCDate() - ((d.getUTCDay() + 1) % 7))
        d.setUTCSeconds(0);
        d.setUTCMinutes(0);
        d.setUTCHours(0);
        var i = d.getTime();
        do {
            // when we don't set yaxis the rectangle automatically
            // extends to infinity upwards and downwards
            markings.push({ xaxis: { from: i, to: i + 2 * 24 * 60 * 60 * 1000 } });
            i += 7 * 24 * 60 * 60 * 1000;
        } while (i < axes.xaxis.max);

        return markings;
    }
    
    var options = {
        xaxis: { mode: "time" },
        selection: { mode: "x" },
        legend: { position: 'sw' },
        points: { show: true },
        lines: { show: true },
        grid: { markings: weekendAreas }
    };

    var plot = $.plot($("#placeholder"), d, options);

    var overview = $.plot($("#overview"), [d1, d2, d3, d4], {
        lines: { show: true, lineWidth: 1 },
        shadowSize: 0,
        xaxis: { ticks: [], mode: "time" },
        selection: { mode: "x" }
    });

    $("#placeholder").bind("plotselected", function (event, ranges) {
        // do the zooming
        plot = $.plot($("#placeholder"), d,
                      $.extend(true, {}, options, {
                          xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to }
                      }));

        // don't fire event on the overview to prevent eternal loop
        overview.setSelection(ranges, true);
    });

    $("#overview").bind("plotselected", function (event, ranges) {
        plot.setSelection(ranges);
    });
});
</script>

Javascripts und JRails

flot selbst benötigt jQuery. Eine elegante und schnelle Art der Einbettung ist es, das jRails-plugin zu installieren – es bringt jQuery mit:

script/plugin install http://ennerchi.googlecode.com/svn/trunk/plugins/jrails

Das fügt die rechten Files in public/javascripts ein. Es fehlt nur noch das jquery.flot.js:

  1. jrails.js
  2. jquery-ui.js
  3. jquery.flot.js
  4. jquery.js

das html-Layout

Zum Schluss müssen die Scripte im Layout aktivert werden – app/views/layouts/application.html.erb:

1
2
3
4
...
  <%= javascript_include_tag :defaults %>
  <%= javascript_include_tag 'jquery.flot' %>
...

Weblinks

  1. github.com/fwoeck/analytics
  2. introducing-garb-access-the-google-analytics-data-export-api-with-ruby
  3. github.com/vigetlabs/garb
  4. code.google.com/p/flot
  5. code.google.com/gdataReferenceDimensionsMetrics

autocomplete vs. autocomplete 0

Posted by fwoeck
on Tuesday, April 21

Nachdem ich mein aktuelles Railsprojekt von Prototype auf jQuery umgesattelt hatte, war die autocomplete-Funktion für die Formularelemente gebrochen.

Also altes Plugin raus, neues rein – z. B. das von Dylan Verheul. Man muss sich die einzelnen Files ein bisschen zusammenklauben:

cd public/javascripts/
wget http://dyve.net/jquery/js/jquery.autocomplete.js

cd ../images/
wget http://dyve.net/jquery/img/indicator.gif

cd ../stylesheets/
vi ui.autocomplete.css

mit:

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
.ac_input {
  width: 200px;
}
.ac_results {
  padding: 0px;
  border: 1px solid WindowFrame;
  background-color: Window;
  overflow: hidden;
}

.ac_results ul {
  width: 100%;
  list-style-position: outside;
  list-style: none;
  padding: 0;
  margin: 0;
}

.ac_results iframe {
  display:none;
  display/**/:block;
  position:absolute;
  top:0;
  left:0;
  z-index:-1;
  filter:mask();
  width:3000px;
  height:3000px;
}

.ac_results li {
  margin: 0px;
  padding: 2px 5px;
  cursor: pointer;
  display: block;
  width: 100%;
  font: menu;
  font-size: 12px;
  overflow: hidden;
}
.ac_loading {
  background : url('/images/indicator.gif') right center no-repeat;
}
.ac_over {
  background-color: Highlight;
  color: HighlightText;
}

Und schließlich das alte Plugin gelöscht:

cd ../..
rm -rf vendor/plugins/auto_complete/

ein neues Setup

Die neuen Files wollen eingebettet sein. Im Head:

1
2
<%= javascript_include_tag 'jquery.autocomplete' %>
<%= stylesheet_link_tag 'ui.autocomplete' %>

und die Views

Die Formularfelder baue ich jetzt, wie unten gezeigt. Das ist ganz sicher nicht so, wie von den ursprünglichen autocomplete-Helfern gedacht, denn die answer.id wird hier ziemlich un-RESTmäßig als Parameter übergeben – aber es funktioniert für mich…:

1
2
3
4
5
6
7
<input autocomplete="off" id="answer_<%= answer.id -%>" 
      name="answer[<%= answer.id -%>]" type="text" value="<%= answer.value_str -%>" />

<script type="text/javascript">
  $("input#answer_<%= answer.id -%>").autocomplete("/projects/<%= @project.id -%>
                     /proforms/auto_complete_for_answer_id?answer=<%= answer.id -%>")
</script>

Die Verschachtelung mit projects stammt von der Route her:

1
2
3
4
map.resources :projects do |projects|
  projects.resources :proforms, :collection => 
                   { :auto_complete_for_answer_id => :get }
...

jetzt der Controller

Dies ist die Funktion im proforms-Controller – sie sucht vergleichbare Ausdrücke in meinem Answer-Modell. Wie man hier sieht, übergibt das jQuery-autocomplete-Plugin den Suchstring als q-Parameter:

1
2
3
4
5
6
7
def auto_complete_for_answer_id
  search = params[:q]
  answer_id = params[:answer].to_i
  @vals = Answer.find(answer_id).entity.footprints.map(&:value_str).select{|v| 
                                                v =~ /#{search}/i}.uniq
  render :partial => "q_string"
end

Das Partial “q_string” rendert nur eine Ascii-Liste von Wertepaaren, und das müsste eigentlich schon Alles sein:

1
2
3
<% for val in @vals.to_a -%>
  <%= val -%>|<%= val %>
<% end -%>

Weblinks

  1. dyve.net/jquery/?autocomplete
  2. www.bassistance.de/jquery-plugin-autocomplete
  3. update-on-rails-jquery-autocomplete

Tausche Prototype gegen jQuery 0

Posted by fwoeck
on Tuesday, April 21
  1. jQuery ist irgendwie unobtrusiv und schlank
  2. die Mobilplattform, mit der ich grade angefangen habe zu experimentieren benutzt jQuery
  3. für Rails gibt es jRails, wenn man will

Zeit für einen Wechsel! Ich fang’ mit dem laufenden Railsprojekt an.

Um die gewohnten Helfer in Rails und RJS weiter zu unterstützen und den Migrationsaufwand klein zu halten installiere ich jRails:

script/plugin install http://ennerchi.googlecode.com/svn/trunk/plugins/jrails

Manuell würde man jetzt die …/application.html.erb ändern:

1
2
3
<script src="/javascripts/jquery.js" type="text/javascript"></script>
<script src="/javascripts/jquery-ui.js" type="text/javascript"></script>
<script src="/javascripts/jrails.js" type="text/javascript"></script>

Falls man allerdings die übliche Zeile zum Einbetten der Skripte verwendet:


<%= javascript_include_tag :defaults, :cache => 'jquery' %>

ist es damit schon getan – anstelle der Prototype-Files werden nun die neuen geladen. Das

:cache => 'jquery'

veranlasst Rails in der Produktion die Skripte zu einer einzigen .js-Datei zusammenzufassen, um http-Requests zu sparen.

Aus unklaren Gründen funktioniert die Sache mit dem cache… in der Produktivumgebung gut, allerdings führt sie in meiner Selenium-Testumgebung zu Problemen. Gelegentlich tauchen solche Fehler auf:

$("#project_active_1").delayedObserver is not a function

Timingproblem? Deshalb deaktiviere ich das Caching erstmal wieder.

Einiges anders

Hier folgen ein paar Änderungen, die ich anbringen musste, um die Tests zu passieren:

Alle Helferaufrufe benötigen nun ein # für ids im Selektor – die waren vorher nicht nötig:


<%= link_to_function "Person anlegen", "$('#personselect').toggle();" %>

.up() wird .parent() oder .parents():

1
2
3
4
<div class="delme">
  <p>
    <%= link_to_function "<img alt='Bild' src='delete.png' />", 
        "$(this).up('.delme').remove()" %>
wird:
1
2
3
4
<div class="delme">
  <p>
    <%= link_to_function "<img alt='Bild' src='delete.png' />", 
        "$(this).parents('.delme').remove()" %>

.value= wird .val()


$('#answer_#{answer.id}_blank_value_date').value = '1';
wird:

$('#answer_#{answer.id}_blank_value_date').val('1');

Der Autocompleter streikt

Der autocompleter ist ja schon vor längerer Zeit in ein Plugin ausgegliedert worden und mein Viewcode

1
2
3
4
5
6
7
8
<script type="text/javascript">
  //<![CDATA[ 
   var answer_<%= answer.id -%>_auto_completer = new Ajax.Autocompleter(
    'answer_<%= answer.id -%>', 'answer_<%= answer.id -%>_auto_complete', 
    '/projects/<%= @project.id -%>/proforms/auto_complete_for_answer_id', 
    {method:'get'}) 
  //]]>
</script>

erzeugt eine Fehlermeldung “Ajax is not defined” im Firebug. Arghh, na ich denke, das wird eigener Post.

Weblinks

  1. ennerchi.com/projects/jrails
  2. dev.jqueryui.com/browser/tags/latest

Fileuploads mit dem "form_remote_tag"... 0

Posted by fwoeck
on Friday, February 27

... klappen leider nicht: JScript darf nicht!

Was kann man tun in einer Situation, in der man viele einzelne Forms auf einer Seite unterhält, die alle von derselben Codeschleife generiert werden, von denen die meisten keine Fileuploads enthalten und via form_remote_tag angeschoben werden können, aber einige eben doch, so dass diese den konventionellen form_tag verwenden müssen?

Noch mal deutlicher – die Situation:

1
2
3
4
5
6
<% form_tag project_path(@project), :method => :put, :multipart => true do %>
  ... Code der den Forminhalt erzeugt ...
  ... wird pro Seite mehrfach durchlaufen ...
  ... Ein paar Male enthält er Fileuploads, ...
  ... ein paar Male nicht ...
<% end %>

Im form_remote_tag bleibt das :multipart => true wirkungslos. Die Frage, welcher Tag für einen Durchlauf benutzt werden soll, hängt in diesem Beispiel vom jeweiligen Inhalt ab, nämlich ob es ein Fileupload-Feld in diesem Form gibt.

Die eleganteste Lösung, die ich dafür gefunden habe ist, den Form-kopf konditional zu gestalten. Da in den neueren Railsversionen die Formtags i.d.R. einen Block mitbekommen, würden sich dadurch die Form- und die Blockgrenzen der bedingenden if-Schleife überschneiden: geht so nicht.

Also lässt man den Block weg (das do im tag), ersetzt das abschließende <% end %> durch ein </form> und vergisst nicht, den Form-Tags ein <%= zu verpassen, da sie sonst nämlich nichts mehr ausgeben wollen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<% unless actual_form.elements.include?("upload_field") %>

  <%= form_remote_tag :url => "/projects/#{@project.id}", :method => :put, \
    :loading => "Toggle.display('justsaving_#{group.id}');", :complete => \
    "Toggle.display('justsaving_#{group.id}');" %>

<% else %>

  <%= form_tag project_path(@project), :method => :put, :multipart => true %>

<% end %>
  ... Code der den Forminhalt erzeugt ...
  ... wird pro Seite mehrfach durchlaufen ...
</form>

Seriennummern in 42 Zeichen: redo 0

Posted by fwoeck
on Wednesday, February 18

Eine smarte Anwendung des redos-Befehls sehen wir in diesem Einzeiler von Marcus Holm:

>> (0..9).map{rand(?z).chr[/[^_\W]/]||redo}*"" 
=> "H8epSomSCc"

redo springt (ähnlich wie skip) zum Anfang eines loop oder eines Blocks. Der Unterschied besteht darin, dass redo die selbe Iteration erneut ausführt und nicht die nächste. Ein Beispiel:

1
2
3
4
5
6
7
8
9
10
11
12
puts "Please enter the first word you think of"
words = %w(apple banana cherry)
response = words.collect do |word|
  # Control returns here when redo is executed
  print word + "> "               # Prompt the user
  response = gets.chop            # Get a response
  if response.size == 0           # If user entered nothing
    word.upcase!                  # Emphasize the prompt with uppercase
    redo                          # And skip to the top of the block
  end
  response                        # Return the response
end

In seinem Blog werden dafür praktische Beispiele gegeben: tailin-ruby.html

Nebensache: Diagramm-Engine mit JScript

Bei der Darstellung seiner Benchmarkergebnisse verwendet der Autor einen recht hübschen Diagrammmechanismus: Diagramm-Beispiel

Das Diagramm ist interaktiv klick- und ziehbar! Es benutzt JQuery und einen Plotting-Zusatz, um die Grafiken zu generieren.

Weblinks

  1. code.google.com/p/flot
  2. github.com/judofyr/recursive/tree/master

Js- und Css-Kompression etc. 0

Posted by fwoeck
on Monday, February 16

Zur Beschleunigung eines Webpage-Aufbaus lassen sich einige Tricks auf verschiedene Elemente der Inhalte anwenden. Hier handelt es sich im wesentlichen um Kompression. Unten sind einige Quellen und Tools für die Nacharbeit aufgelistet.

Web-Quellen:

  1. yui/compressor
  2. pngcrush
  3. YUI Compressor online

Tool-Examples

YUI-Compressor, entschlackt Js und Css:

java -jar yuicompressor-2.4.2.jar all.js > all_c.js

FreeImage identify zeigt Overheads an:

identify -verbose image.gif

convert konvertiert u.a. gifs nach png:

convert image.gif image.png

pngcrush reduziert pngs:

pngcrush -rem alla -reduce -brute image.png result.png

jpegtran strippt und optimiert Jpegs:

jpegtran -copy none -optimize -perfect src.jpg dest.jpg

auto_complete-Helper selber bauen 0

Posted by fwoeck
on Wednesday, November 12

texttexttext! FIXME

View

<style type="text/css">        
        div.auto_complete {
          width: 350px;
          background: #fff;
        }
        div.auto_complete ul {
          border:1px solid #888;
          margin:0;
          padding:0;
          width:99%;
          list-style-type:none;
        }
        div.auto_complete ul li {
          margin:0;
          padding:2px;
        }
        div.auto_complete ul li.selected {
          background-color: #ffb;
        }
        div.auto_complete ul strong.highlight {
          color: #800; 
          margin:0;
          padding:0;
        }

</style><input autocomplete="off" id="answer_<%= answer.id -%>" 
  name="answer[<%= answer.id -%>]" size="20" type="text" value=
  "<%= answer.value_str -%>" /><div class="auto_complete" id=
  "answer_<%= answer.id -%>_auto_complete"></div><script 
  type="text/javascript">
//<![CDATA[
var answer_<%= answer.id -%>_auto_completer = new 
  Ajax.Autocompleter('answer_<%= answer.id -%>', 'answer_<%= 
  answer.id -%>_auto_complete', '/projects/<%= @project.id -%>
  /proforms/auto_complete_for_answer_id', {method:'get'})
//]]>
</script>

Partial

<ul class="autocomplete_list">
  <% for val in @vals.to_a -%>
    <li class="autocomplete_item" value="<%= val %>"><%= val %></li>
  <% end -%>
</ul>

Controller

def auto_complete_for_answer_id
  search = params[:answer].keys.first
  @vals = Answer.find(search.to_i).entity.footprints.map(&:value_str).
      select{|v| v =~ /#{params[:answer][search]}/i}
  render :partial => "q_string" 
end

Routes

map.resources :projects do |projects|
  projects.resources :proforms, :collection => { :auto_complete_for_answer_id 
      => :get } do |proforms|
    proforms.resources :progroups
  end
end

dynamische Select-Menüs mit JavaScript 0

Posted by fwoeck
on Tuesday, November 04

Wenn man Ajax umgehen möchte, um die Auswahl eines bestimmten Select-Feldes von der Auswahl eines anderen abhängig zu machen, kann man diese Funktion auch mit JavaScript allein auf der Clientseite regeln.

Ein javascript-Controller

Wir legen einen Controller javascript an. Der Trick an der Sache besteht darin, dass wenn der Client später eine URL /javascript/...js aufruft und eine entsprechende Route gesetzt ist, der neue Controller aufgerufen wird und dynamisch ein js-File erzeugen kann.

Hier befüllt der Controller das @jobs-Array, das später mit dem js-Skript auf die einzelnen Customer-Bereiche aufgeteilt wird:

class JavascriptsController < ApplicationController
  def dynamic_jobs
    @jobs = Job.all_cached
  end
end

Die Route dorthin

müsste dann so lauten:

map.connect ':controller/:action.:format'

Der View-Abschnitt

In diesem Falle sollen innerhalb eines Formulars für das Project-Modell die möglichen Jobs von der Auswahl des Customers abhängen:

<% javascript 'dynamic_jobs' %>
...
<% form_for @project do |f| %>
  ...
<p>
  Kunde<br />
  <%= f.collection_select :customer_id, Customer.all_cached, :id, :name, :prompt => "" %>
</p>

<p id="job_field">
  Job<br />
  <%= f.collection_select :job_id, Job.all_cached, :id, :name, :prompt => "" %>
</p>

Das File dynamic_jobs.js.erb

befindet sich im views/javascripts-Ordner. Es erzeugt das js-File mit den Kunden und Jobs. Die Funktion customerSelect teilt die Daten des Arrays in Kunden ein.

var jobs = new Array();
<% for job in @jobs -%>
  jobs.push(new Array(<%= job.customer_id %>, '<%=h job.name %>', <%= job.id %>));
<% end -%>

function customerSelected() {
  customer_id = $('project_customer_id').getValue();
  options = $('project_job_id').options;
  options.length = 1;
  jobs.each(function(job) {
    if (job[0] == customer_id) {
      options[options.length] = new Option(job[1], job[2]);
    }
  });
  if (options.length == 1) {
    $('job_field').hide();
  } else {
    $('job_field').show();
  }
}

document.observe('dom:loaded', function() {
  customerSelected();
  $('project_customer_id').observe('change', customerSelected);
});

und schließlich

benötigen wir noch einen Helper, der das dynamisch erzeugt js-File in das Rendering einbaut – z.B. in der application_helper.rb:

def javascript(*args)
  args = args.map { |arg| arg == :defaults ? arg : arg.to_s }
  content_for(:head) { javascript_include_tag(*args) }
end

Ausgeblendete Formularfelder löschen 0

Posted by fwoeck
on Friday, October 24

Das Umschalten von Sichbarkeiten im View mit toggle() geht schnell, aber nur weil ein div-Element unsichtbar ist, bedeutet dies nicht, dass sein Inhalt beim Formularversand nicht mitgeschickt würde.

Damit der Controller eindeutig wissen kann, welches Feld gewählt wurde, muss man das jeweils ausgeblendete Feld nullen:

<div id="personselect"> 
  <p >
    Person<br />
    <%= select_tag 'project[person_id]', options_for_select([["",""]] + 
             Person.all.map {|w| [w.gname + " " + w.sname, w.id]}), :id => 'personid' %>
  </p>
  <p>
    <%= link_to_function "Person neu anlegen", "$('personselect').toggle(); 
             $('personcreate').toggle(); $('personid').clear();" %>
  </p>
</div>

<div id="personcreate" style="display: none"> 
  <p >
    Vorname Nachname<br />
    <%= text_field_tag 'gname', {}, :size => 10, :id => 'gnameid' %> 
    <%= text_field_tag 'sname', {}, :size => 20, :id => 'snameid' %>
  </p>
  <p>
    <%= link_to_function "Person wählen", "$('personcreate').toggle(); 
             $('personselect').toggle(); $('gnameid').clear(); $('snameid').clear();" %>
  </p>
</div>

Form-Element dynamisch mit link_to_function erzeugen 0

Posted by fwoeck
on Friday, October 10

Ein Link innerhalb eines Formulares soll es dem User ermöglichen, dynamisch mit RJS einen zusätzlichen Formularteil einzublenden.

Man definiert eine Helpermethode für den View:

module EventsHelper

  def add_altdate_link(name)
    link_to_function name do |page|
      page.replace_html :altdate, :partial => 'altdate'
    end
  end

end

und ein entsprechendes altdate-Partial:

<p>
  Terminvorschlag<br />
  <%= date_select :guest, :alt_date, :start_year => Time.now.year, 
                                          :end_year => 1.year.from_now.year %>
</p>

und benutzt sie dann im Formular:

<div id='altdate'>
  <%= add_altdate_link "Terminvorschlag" %>
</div>

Siehe auch : Form-Element dynamisch mit link_to_remote zuladen

Form-Element dynamisch mit link_to_remote zuladen 0

Posted by fwoeck
on Tuesday, October 07

Innerhalb einer RESTful gestalteten Umgebung kann man einfach z.B. die new-Methode eines Controllers verwenden, um dynamisch ein Partial vom Server anzufordern.

_form.html.erb:
<div id='picture'>
  <%= link_to_remote 'Bild ändern', :url => new_martian_path, 
        :update => 'picture', :method => :get %>
</div>
martians_controller.rb:
def new
  @martian = Martian.new
  respond_to do |wants|
    wants.html
    wants.js { render :partial => 'picture' }
  end
end
_picture.html.erb:
<% fields_for :m_image do |img| %>
  <p>
    <b>Image</b>
    <%= img.file_field :uploaded_data %>
  </p>
<% end %>

Siehe auch: Form-Element dynamisch mit link_to_function erzeugen

observe_field mit with => benutzen 0

Posted by fwoeck
on Saturday, September 06

Wichtig: der with => Parameter ist Bestandteil des observe_field-Tags, nicht etwa des text_field-Tags! Hier wird die ISBN-Nummer an den Controller gesendet, um die Informationen eines Buches von der Amazon-Website zu holen.

Abschnitt des Views

<p>
  ISBN-Nummer<br />
  <%= f.text_field :isbn %>
</p>

<%= observe_field 'book_isbn', :url => { :action => 'validate_isbn' }, 
  :on => 'blur', :update => 'book_div', :with => :isbn %>

<div id='book_div'>&nbsp;</div>

Die Methode im Controller

def validate_isbn
  ans = "" 
  url = "http://www.amazon.de/s/ref=nb_ss_eb?__mk_de_DE=%C5M%C5Z%D5%D1&url=
          search-alias%3Dstripbooks&field-keywords=#{params[:isbn]}" 
  res = %x{ wget -O - -q "#{url}" | recode latin1..utf8 }
  doc = Hpricot.parse(res)
  ans = (doc/'#prodImageCell').inner_html
  ans = "&nbsp;" if ans.blank?
  render :text => ans
end

die RESTful Route

map.resources :books, :collection => { :validate_isbn => :post }