Comments (14)
I think it can be fixed here
----------------------
/Users/bradphelan/.rvm/gems/ruby-1.9.2-p180@gobbq/gems/apotomo-1.2.2/lib/apotomo/widget.rb
----------------------
133 def address_for_event(type, options={})
134 options.reverse_merge! :source => name,
135 :type => type,
136 :controller => parent_controller.controller_path # DISCUSS: dependency to parent_controller.
137 end
from apotomo.
Fix turns out to be trivial. Just need to add another route to the apotomo routing
----------------------
/Users/bradphelan/.rvm/gems/ruby-1.9.2-p180@gobbq/gems/apotomo-1.2.2/config/routes.rb
----------------------
1 Rails.application.routes.draw do
2 match ":controller/render_event_response", :to => "#render_event_response", :as => "apotomo_event"
3 match ":controller/:id/render_event_response", :to => "#render_event_response", :as => "apotomo_event"
4 end
from apotomo.
I have an even simpler solution as the above route matching breaks under several circumstances. I have a base widget with the following implementation
# A BaseWidget to apply stuff to widgets across
# the board. All widgets should inherit from this
class ApplicationWidget < Apotomo::Widget
include ActionController::RecordIdentifier # so I can use `dom_id`
def url_for_event type, options={}
p = {}
parent_controller.request.parameters.each do |k,v|
if k.end_with? "_id" or k == "id"
p[k] = v
end
end
super type, p.merge(options)
end
end
What I do is forward any parameters that are either id or _**id* as URL parameters to url_for_event. This catches most cases for needing to render a widget at a specific resource and wanting that resource identifier carried automatically over in the render_event_response call.
from apotomo.
Ah, no ifs, that'll be magic :-) This is definitely a routing problem - we must fix that with Rails.
from apotomo.
This is interesting as it also suggests a solution to state handling between events without using the session. url_for event can be hijacked to add arbitrary parameters to the the event url. I've added the following changes to by base application widget.
class ApplicationWidget < Apotomo::Widget
def self.stateful_option option
@stateful_options ||= []
@stateful_options << option
end
def self.stateful_options
@stateful_options
end
# Make sure that all id or *_id parameters
# are forwarded to the event URL so that
# resources are recovered correctly when
# the event is executed.
def url_for_event type, opts={}
p = HashWithIndifferentAccess.new
parent_controller.request.parameters.each do |k,v|
if k.end_with? "_id" or k == "id"
p[k] = v
end
end
so = {}
self.class.stateful_options.each do |opt|
so[opt] = options[opt]
end
opts = p.merge(opts).merge :state => {
widget_id => so
}
super type, p.merge(opts)
end
def options
unless @stateful_options
@stateful_options = super
self.class.stateful_options.each do |opt|
if params[:state] and params[:state][widget_id]
@stateful_options[opt] = params[:state][widget_id][opt]
end
end
end
@stateful_options
end
end
and I can then declare options to persist into the events
class PublicEventsWidget < ApplicationWidget
responds_to_event :refresh
stateful_option :past
<snip>
end
a call to
url_for_event(:refresh)
will generate the URL
http://localhost:3000/events/render_event_response?radius=1000&source=public_events&state%5Bpublic_events%5D%5Bpast%5D=&type=refresh
or when decoded
http://localhost:3000/events/render_event_response?radius=1000&source=public_events&state[public_events][past]=true&type=refresh
The overridden options method processes the params list looking for an item with the same widget_id and key in the state parameter and then adds this to the options.
The URL length limit is about 2000 characters so this approach has it's limit.
from apotomo.
The other way to do it would be to hijack widget_div and inject json into an html5 data attribute and pick the data up appended to the ajax post.
<div class="widget" id="public_event" data-state="{past:true}">
</snip>
<a href="/events/render_event_response" data-remote="true" data-widget="true"> foo </a>
</div>
then we can hook the jquery ujs driver with
$(document).ready =>
apply = =>
$('body').on 'ajax:before', '*', (e)->
e.stopPropagation()
state = $(@).closest(".widget").data('state')
$(@).data('params', state)
apply()
Just verified that the above works very nicely in my code.
from apotomo.
Actually I had to override widget_div with the following implementation to get the above working
module Apotomo::Rails::ViewHelper
def widget_tag(tag, options={}, &block)
if options[:class]
options[:class] = "#{options[:class]} widget"
options['data-state'] = stateful_options.to_json
end
options.reverse_merge!(:id => widget_id)
content_tag(tag, options, &block)
end
def widget_div(options={}, &block)
widget_tag "div", options, &block
end
end
from apotomo.
The above solution ( proposed by me ) is wrong. I don't believe it is the widgets job to specify if any of it's options are persisted or not. I think this should be done in the by whatever creates the widget. For example. I have a standard rails controller that needs a widget.
class EventsController < ApplicationController
has_widgets do
root << widget(:events ,
'my_events',
:geolocate => false,
:filter => my_events,
:title => :as_host,
:past => params[:past] )
end
end
Now the problem here is that if we call url_for_event in the context of any of the views of :events widget then the handler will be missing the :past parameter. Who's responsibility is it to make the :past parameter stateful. I would argue that it is not the widgets responsibility as in other circumstances I might hard code the :past parameter or have some other algorithm based on the position of the moon etc to set it. Obviously it is whoever/whatever instantiates the widget to decide which parameters are stateful and which are not. I propose a simple solution for this
class EventsController < ApplicationController
has_widgets do
root << widget(:events ,
'my_events',
:geolocate => false,
:filter => my_events,
:title => :as_host,
:past => remember(params[:past] ) )
end
end
In fact I would say that the only widget that should ever use the remember function would be a root widget. All other widgets should derive their state from either the database or the state of their parent widget.
I have a solution that works for this based on some of the code of my previous notes.
We need a javascript helper
$(document).ready =>
rootWidget = (leaf)=>
leaf.parents(".widget").last()
apply = =>
$('body').on 'ajax:before', '*', (e)->
target = $(@)
e.stopPropagation()
# Only the root widget has state
# parameters
widget = rootWidget(target)
state = widget.data 'state'
id = widget.attr 'id'
params = target.data('params')
params = {} unless params?
params['state'] = {}
params['state'][id] = state
target.data('params', params)
apply()
Our widget_tag and widget_div helpers now look like
module Apotomo::Rails::ViewHelper
def widget_tag(tag, options={}, &block)
if options[:class]
options[:class] = "#{options[:class]} widget"
options['data-state'] = stateful_options.to_json
else
options[:class] = "widget"
end
options.reverse_merge!(:id => widget_id)
content_tag(tag, options, &block)
end
def widget_div(options={}, &block)
widget_tag "div", options, &block
end
end
and we need to override options and provide stateful_options methods within the Widget (sub)class
def stateful_options
options.select do |k,v|
v.is_a? Apotomo::StatefulOption
end
end
helper_method :stateful_options
def options
unless @options_with_state
@options_with_state = super
if params[:state]
if params[:state][widget_id]
@options_with_state = @options_with_state.with_indifferent_access.merge params[:state][widget_id].with_indifferent_access
end
end
end
@options_with_state
end
def options
unless @options_with_state
@options_with_state = super
if params[:state]
if params[:state][widget_id]
@state = params[:state][widget_id].each do |k,v|
@options_with_state[k.to_sym] = Apotomo::StatefulOption.new(v)
end
end
end
end
@options_with_state
end
Finally we need to create the remember function which is pretty easy as we just use the SimpleDelegator ruby class to mark it. There are other way to do this I guess but this works.
# lib/apotomo/stateful_option.rb
require 'delegate'
class Apotomo::StatefulOption < SimpleDelegator
end
class ActionController::Base
def remember val
Apotomo::StatefulOption.new(val) unless val.blank?
end
end
I think those are all the moving parts and it works very nicely for me now.
from apotomo.
Good stuff :-) We were already designing a stateful engine for Apotomo with adapters for session storage and the way you propose where options get saved in the page and then appended to URLs. Basically it would work like this (note that it is the widget's job to define stateful attributes!).
class CommentsWidget < Apotomo::Widget
include Stateful
stateful_attrs :comment_ids, :user_id
end
apotomo-stateful
would now worry about making those instance variables available in the next request. This is very close to what you wrote.
The biggest problem I have is writing the JavaScript that appends data to all URLs - maybe you can help out here?
from apotomo.
I'm still not sure about defining state attributes within the widget. Perhaps some use cases would clarify. I think pagination is probably an internal state like use case.
It's pretty easy to attach json to html objects. In rails ( haml )
.widget{ :id => "comment_widget" :data-state => @state.to_json}
= some_cool_widget_stuff
In javascript ( coffeescript)
# Find a widget
widget = $("#comment_widget")
# Get it's state data as json
state = widget.data('state')
Sending back json with a remote link in
%a{href=>url_event_for(:click), :remote => true, :data-param=>@state.json}
Note that the rails UJS driver automagically picks up the html5 data param and delivers it as
extra post / get data. That is the basics of it. Most of the more complex code I wrote above
was just about correctly namespacing and collecting the data.
A small change to the javascript code above will collect state data from all widgets
from the root of the widget tree and add it to the target element for ujs to deliver
to the server. ( Not tested but I think it will work )
$(document).ready =>
allWidgetsFromRoot = (leaf)=>
# First find the root widget then find
# all widgets from the root
root = leaf.parents(".widget").last()
all_children = root.find(".widget")
apply = =>
$('body').on 'ajax:before', '*', (e)->
target = $(@)
e.stopPropagation()
# Preserve hard coded params
params = target.data('params')
params = {} unless params?
params['state'] = {}
# Collect all state parameters and
# provide them to the UJS driver via
# the 'data-params' attribute
allWidgetsFromRoot(target).map (widget)=>
state = widget.data 'state'
id = widget.attr 'id'
params['state'][id] = state
target.data('params', params)
apply()
I'd be happy to help out :)
from apotomo.
@bradphelan and @apotonick what is the status of this work? I would love to get this into apotomo and working. I'm a bit sad to see that apotomo seems to have stalled while cells is progressing ahead. What is the reason behind this?
Cheers!
from apotomo.
The thing is that Cells is becoming more and more popular whereas Apotomo is still a "secret treasure", used by many people, but not as actively propagated as Cells since it completely changes the "Rails way". I'd love to see it evolve more but I have 4 big gems to maintain and I only have limited resources. If you're up for polishing it with my help I am ready to go!!!
from apotomo.
I'm ready to go! I'm using it in a large scale project that is split up in multiple engines. I love the more OO approach of Cells and Widgets instead of the very "flat" Rails way, which has little to do with OOP, even with the Rails 3 architecture, except for a very primitive use of MVC (also the idea of "thick" models is "sick" IMO).
Seems like script programmers prefer the procedural approach, managing their code by simply subdividing into modules, causing all kinds of mayhem and disorganisation IMO. Typical beginner approach I think.
If you look at my fork, you will see I have made some major changes, such as restructuring how the widget tree is built. This is still an experiment. I would also like to see some of the ideas discussed in this particular issue (67) to be put into action. What are your ideas for improvement and main concerns?
from apotomo.
On a side note, I was just reading through the Cells docs and wiki to get a better grasp of the fundamentals.
I stumbled onto this:
`Using the *_url helpers implies accessing the request instance, which kinda breaks encapsulation.``
Could you explain what you mean by this? I would assume you are trying to say, that inside a cell there would normally be no need to generate urls, since it should only render the views that relate directly to its actions?
from apotomo.
Related Issues (20)
- widget inside engine with namespace, problem using url_for_event HOT 2
- Setup is run after "has_widgets" HOT 1
- License missing from gemspec HOT 1
- rspec [not found] HOT 3
- arguments passed to url_for can't be handled (Rails 4 upgrade) HOT 3
- render_widget helper collision HOT 1
- parent_controller in tests HOT 7
- Add :patch to routes for Rails 4 HOT 7
- Add append method (similar to replace method) HOT 6
- uninitialized constant Hooks::InheritableAttribute (NameError) HOT 2
- Wrong number of arguments (0 for 1) HOT 16
- Action Dispatch error when trying to generate a widget HOT 2
- Custom response code for ajax events support HOT 3
- Access to cookies in a widget HOT 5
- undefined method `setup_view_paths!' HOT 3
- Route related error when starting server HOT 4
- apotomo csrf vulnerability HOT 11
- uninitialized constant Cell::Rails (NameError) - Cell 4.0.0 HOT 4
- Minitest got error message about document_root_element
- Maintained? HOT 6
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from apotomo.