Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit of the Ad Banners extension

  • Loading branch information...
commit 7a6c0d36d375ff2ff645e65068a8cf2b072ca6d3 0 parents
@davec authored
Showing with 958 additions and 0 deletions.
  1. +3 −0  README
  2. +120 −0 Rakefile
  3. +25 −0 ad_banners_extension.rb
  4. +3 −0  app/controllers/admin/ad_banners_controller.rb
  5. +2 −0  app/helpers/admin/ad_banners_helper.rb
  6. +10 −0 app/models/ad_banner.rb
  7. +26 −0 app/views/admin/ad_banners/_assets_container.html.haml
  8. +10 −0 app/views/admin/ad_banners/_bucket.html.haml
  9. +7 −0 app/views/admin/ad_banners/_bucket_asset.html.haml
  10. +33 −0 app/views/admin/ad_banners/_form.html.haml
  11. +9 −0 app/views/admin/ad_banners/edit.html.haml
  12. +42 −0 app/views/admin/ad_banners/index.html.haml
  13. +9 −0 app/views/admin/ad_banners/new.html.haml
  14. +21 −0 app/views/admin/ad_banners/remove.html.haml
  15. +17 −0 db/migrate/20090613022118_create_ad_banners.rb
  16. +25 −0 lib/ad_banners_admin_ui.rb
  17. +28 −0 lib/tasks/ad_banners_extension_tasks.rake
  18. BIN  public/images/admin/ad_banners/ad.png
  19. BIN  public/images/admin/ad_banners/new-ad.png
  20. +31 −0 public/javascripts/admin/ad_banners.js
  21. +361 −0 public/javascripts/lowpro.js
  22. +64 −0 public/stylesheets/admin/ad_banners.css
  23. +10 −0 spec/controllers/admin/ad_banners_controller_spec.rb
  24. +11 −0 spec/helpers/admin/ad_banners_helper_spec.rb
  25. +49 −0 spec/models/ad_banner_spec.rb
  26. +6 −0 spec/spec.opts
  27. +36 −0 spec/spec_helper.rb
3  README
@@ -0,0 +1,3 @@
+= Ad Banners
+
+Description goes here
120 Rakefile
@@ -0,0 +1,120 @@
+# I think this is the one that should be moved to the extension Rakefile template
+
+# In rails 1.2, plugins aren't available in the path until they're loaded.
+# Check to see if the rspec plugin is installed first and require
+# it if it is. If not, use the gem version.
+
+# Determine where the RSpec plugin is by loading the boot
+unless defined? RADIANT_ROOT
+ ENV["RAILS_ENV"] = "test"
+ case
+ when ENV["RADIANT_ENV_FILE"]
+ require File.dirname(ENV["RADIANT_ENV_FILE"]) + "/boot"
+ when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../")}/config/boot"
+ else
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../")}/config/boot"
+ end
+end
+
+require 'rake'
+require 'rake/rdoctask'
+require 'rake/testtask'
+
+rspec_base = File.expand_path(RADIANT_ROOT + '/vendor/plugins/rspec/lib')
+$LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
+require 'spec/rake/spectask'
+# require 'spec/translator'
+
+# Cleanup the RADIANT_ROOT constant so specs will load the environment
+Object.send(:remove_const, :RADIANT_ROOT)
+
+extension_root = File.expand_path(File.dirname(__FILE__))
+
+task :default => :spec
+task :stats => "spec:statsetup"
+
+desc "Run all specs in spec directory"
+Spec::Rake::SpecTask.new(:spec) do |t|
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
+ t.spec_files = FileList['spec/**/*_spec.rb']
+end
+
+namespace :spec do
+ desc "Run all specs in spec directory with RCov"
+ Spec::Rake::SpecTask.new(:rcov) do |t|
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
+ t.spec_files = FileList['spec/**/*_spec.rb']
+ t.rcov = true
+ t.rcov_opts = ['--exclude', 'spec', '--rails']
+ end
+
+ desc "Print Specdoc for all specs"
+ Spec::Rake::SpecTask.new(:doc) do |t|
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
+ t.spec_files = FileList['spec/**/*_spec.rb']
+ end
+
+ [:models, :controllers, :views, :helpers].each do |sub|
+ desc "Run the specs under spec/#{sub}"
+ Spec::Rake::SpecTask.new(sub) do |t|
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
+ t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
+ end
+ end
+
+ # Hopefully no one has written their extensions in pre-0.9 style
+ # desc "Translate specs from pre-0.9 to 0.9 style"
+ # task :translate do
+ # translator = ::Spec::Translator.new
+ # dir = RAILS_ROOT + '/spec'
+ # translator.translate(dir, dir)
+ # end
+
+ # Setup specs for stats
+ task :statsetup do
+ require 'code_statistics'
+ ::STATS_DIRECTORIES << %w(Model\ specs spec/models)
+ ::STATS_DIRECTORIES << %w(View\ specs spec/views)
+ ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers)
+ ::STATS_DIRECTORIES << %w(Helper\ specs spec/views)
+ ::CodeStatistics::TEST_TYPES << "Model specs"
+ ::CodeStatistics::TEST_TYPES << "View specs"
+ ::CodeStatistics::TEST_TYPES << "Controller specs"
+ ::CodeStatistics::TEST_TYPES << "Helper specs"
+ ::STATS_DIRECTORIES.delete_if {|a| a[0] =~ /test/}
+ end
+
+ namespace :db do
+ namespace :fixtures do
+ desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y"
+ task :load => :environment do
+ require 'active_record/fixtures'
+ ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'spec', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
+ Fixtures.create_fixtures('spec/fixtures', File.basename(fixture_file, '.*'))
+ end
+ end
+ end
+ end
+end
+
+desc 'Generate documentation for the ad_banners extension.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'AdBannersExtension'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
+
+# For extensions that are in transition
+desc 'Test the ad_banners extension.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+# Load any custom rakefiles for extension
+Dir[File.dirname(__FILE__) + '/tasks/*.rake'].sort.each { |f| require f }
25 ad_banners_extension.rb
@@ -0,0 +1,25 @@
+# Uncomment this if you reference any of your controllers in activate
+# require_dependency 'application'
+
+class AdBannersExtension < Radiant::Extension
+ version "0.1"
+ description "Manage ad banners"
+ url "http://yourwebsite.com/ad_banners"
+
+ define_routes do |map|
+ map.namespace :admin, :member => { :remove => :get } do |admin|
+ admin.resources :ad_banners
+ end
+ end
+
+ def activate
+ Radiant::AdminUI.send :include, AdBannersAdminUI unless defined? admin.ad_banner
+ admin.ad_banner = Radiant::AdminUI.load_default_ad_banner_regions
+
+ admin.tabs.add "Ads", "/admin/ad_banners", :after => "Layouts", :visibility => [:all]
+ end
+
+ def deactivate
+ end
+
+end
3  app/controllers/admin/ad_banners_controller.rb
@@ -0,0 +1,3 @@
+class Admin::AdBannersController < Admin::ResourceController
+ model_class AdBanner
+end
2  app/helpers/admin/ad_banners_helper.rb
@@ -0,0 +1,2 @@
+module Admin::AdBannersHelper
+end
10 app/models/ad_banner.rb
@@ -0,0 +1,10 @@
+class AdBanner < ActiveRecord::Base
+ default_scope :order => 'name ASC'
+
+ belongs_to :asset
+
+ validates_presence_of :name, :asset_id#, :link_url
+ validates_format_of :link_url, :allow_blank => true,
+ :with => /\Ahttps?:\/\/.+\z/,
+ :message => "doesn't look like a URL"
+end
26 app/views/admin/ad_banners/_assets_container.html.haml
@@ -0,0 +1,26 @@
+- @stylesheets << 'admin/assets'
+- include_javascript 'lowpro'
+- include_javascript 'admin/assets'
+- include_javascript 'admin/ad_banners'
+- include_javascript 'dragdrop'
+
+#assets-container
+ #asset-tabs
+ - render_region :asset_tabs do |asset_tabs|
+ -# asset_tabs.upload_tab do
+ %a.asset-tab{ :href => "#upload-assets", :id => "tab_upload-assets" } Upload
+ - asset_tabs.bucket_tab do
+ %a.asset-tab{ :href => "#bucket", :id => "tab_bucket" } Bucket
+
+ #assets
+ #bucket.pane
+ - render_region :bucket_pane do |bucket_pane|
+ - bucket_pane.bucket_notes do
+ %p
+ %span.note The Bucket provides temporary storage for your items.
+ %span#clear-bucket
+ = link_to_remote('Clear Bucket', {:url => clear_bucket_url} )
+ - bucket_pane.bucket do
+ = render :partial => "bucket"
+ - bucket_pane.bucket_bottom do
+ .clear
10 app/views/admin/ad_banners/_bucket.html.haml
@@ -0,0 +1,10 @@
+%ul#bucket_list
+ - if session[:bucket]
+ - session[:bucket].each do |url, info|
+ = render :partial => 'bucket_asset', :locals => { :asset_url => url, :asset_id => info[:id], :asset_type => info[:type], :asset_title => info[:title], :asset_thumbnail =>info[:thumbnail], :page => @page }
+ - else
+ %li
+ %p.note Your bucket is empty
+
+
+
7 app/views/admin/ad_banners/_bucket_asset.html.haml
@@ -0,0 +1,7 @@
+%li
+
+ %div{ :class => "#{asset_type} asset", :id => "bucket_#{asset_id}" }
+ = link_to image_tag(asset_thumbnail), asset_url, :title => asset_title, :class => "bucket_link"
+
+ .info{ :id => "#{asset_id}_info" }
+ = link_to_function image_tag('/images/assets/add.png'), "AdBanner.attach($('bucket_#{asset_id}'), $('image-thumbnail'))", { :class => 'add_asset', :title => 'Attach to ad' }
33 app/views/admin/ad_banners/_form.html.haml
@@ -0,0 +1,33 @@
+- thumb_width, thumb_height = Asset.thumbnail_sizes[:thumbnail].first.split(/\D/)
+- form_for [:admin, @ad_banner] do |f|
+ .form-area
+ #ad_banner_form_area
+ #ad_banner-data
+ %p.title
+ %label{:for => 'ad_banner_name'} Name
+ = f.text_field :name, :class => 'textbox', :maxlength => 255
+ %p.title
+ %label{:for => 'ad_banner_asset_id'} Image
+ %span#image-thumbnail{ :style => "width:#{thumb_width}px;height:#{thumb_height}px;" }
+ - if @ad_banner.asset.nil?
+ %span.note Drag an image from the assets bucket to here
+ - else
+ = image_tag(@ad_banner.asset.thumbnail(:thumbnail))
+ = f.hidden_field :asset_id
+ %p.title
+ %label{:for => 'ad_banner_link_url'} Link URL
+ = f.text_field :link_url, :class => 'textbox', :maxlength => 255
+ %p.title
+ %label{:for => 'ad_banner_weight'}
+ Weight
+ %span.note (The greater the weight, the more likely the ad banner is to appear.)
+ = f.select :weight, (1..10).to_a
+ %p.title
+ %label{:for => 'ad_banner_link_target'} Target Window
+ = f.select :link_target, [ [ 'Default', '' ], [ 'New Window', '_blank' ], [ 'Same Window', '_top' ] ]
+ = javascript_tag "$('ad_banner_name').activate()"
+ %p.buttons
+ = save_model_button(@ad_banner)
+ = save_model_and_continue_editing_button(@ad_banner)
+ or
+ = link_to('Cancel', admin_ad_banners_url)
9 app/views/admin/ad_banners/edit.html.haml
@@ -0,0 +1,9 @@
+- include_stylesheet 'admin/ad_banners'
+= render_region :top
+
+- render_region :main do |main|
+ - main.edit_header do
+ %h1 Edit Ad Banner
+
+ - main.edit_form do
+ = render :partial => 'form'
42 app/views/admin/ad_banners/index.html.haml
@@ -0,0 +1,42 @@
+- include_stylesheet 'admin/ad_banners'
+
+%h1 Ad Banners
+
+%p
+ Click on an ad banner name below to edit it, or click <code>Remove</code> to delete it.
+
+%table#ad_banners.index
+ %thead
+ %tr
+ %th.name
+ Name
+ %th.image
+ Image Preview
+ %th.weight
+ Weight
+ %th.url
+ URL
+ %th.modify
+ Modify
+ %tbody
+ - if @ad_banners.empty?
+ %tr
+ %td.note{ :colspan => '5' }
+ No Ad Banners
+ - else
+ - @ad_banners.each do |ad_banner|
+ %tr.node.level-1
+ %td.name
+ = image('ad_banners/ad')
+ = link_to(h(ad_banner.name), edit_admin_ad_banner_url(ad_banner))
+ %td.image
+ = image_tag(ad_banner.asset.thumbnail(:icon))
+ %td.weight
+ = ad_banner.weight
+ %td.url
+ = link_to ad_banner.link_url
+ %td.remove
+ = link_to(image('remove', :alt => 'Remove Ad Banner'), remove_admin_ad_banner_url(ad_banner))
+
+%p
+ = link_to(image('ad_banners/new-ad', :alt => 'New Ad Banner'), new_admin_ad_banner_url)
9 app/views/admin/ad_banners/new.html.haml
@@ -0,0 +1,9 @@
+- include_stylesheet 'admin/ad_banners'
+= render_region :top
+
+- render_region :main do |main|
+ - main.edit_header do
+ %h1 New Ad Banner
+
+ - main.edit_form do
+ = render :partial => 'form'
21 app/views/admin/ad_banners/remove.html.haml
@@ -0,0 +1,21 @@
+- include_stylesheet 'admin/ad_banners'
+
+%h1 Remove Ad Banner
+
+%p Are you sure you want to <strong class="warning">permanently remove</strong> the following ad banner?
+
+%table#ad_banners.index
+ %tbody
+ %tr.node.level-1
+ %td.name
+ = h @ad_banner.name
+ %td.image
+ = image_tag(@ad_banner.asset.thumbnail(:thumbnail))
+ %td.link_url
+ = @ad_banner.link_url
+
+- form_for [:admin, @ad_banner], :html => { :method => :delete } do
+ %p.buttons
+ %input.button{ :type => 'submit', :value => 'Delete Ad Banner' }/
+ or
+ = link_to('Cancel', admin_ad_banners_url)
17 db/migrate/20090613022118_create_ad_banners.rb
@@ -0,0 +1,17 @@
+class CreateAdBanners < ActiveRecord::Migration
+ def self.up
+ create_table :ad_banners do |t|
+ t.string :name
+ t.string :link_url
+ t.string :link_target
+ t.integer :asset_id
+ t.integer :weight
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :ad_banners
+ end
+end
25 lib/ad_banners_admin_ui.rb
@@ -0,0 +1,25 @@
+module AdBannersAdminUI
+
+ def self.included(base)
+ base.class_eval do
+
+ attr_accessor :ad_banner
+ alias_method :ad_banners, :ad_banner
+
+ protected
+
+ def load_default_ad_banner_regions
+ returning OpenStruct.new do |ad_banner|
+ ad_banner.edit = Radiant::AdminUI::RegionSet.new do |edit|
+ edit.main.concat %w{edit_header edit_form assets_container}
+ edit.bucket_pane.concat %w{bucket_notes bucket bucket_bottom}
+ edit.asset_tabs.concat %w{bucket_tab}
+ end
+ ad_banner.new = ad_banner.edit
+ end
+ end
+
+ end
+
+ end
+end
28 lib/tasks/ad_banners_extension_tasks.rake
@@ -0,0 +1,28 @@
+namespace :radiant do
+ namespace :extensions do
+ namespace :ad_banners do
+
+ desc "Runs the migration of the Ad Banners extension"
+ task :migrate => :environment do
+ require 'radiant/extension_migrator'
+ if ENV["VERSION"]
+ AdBannersExtension.migrator.migrate(ENV["VERSION"].to_i)
+ else
+ AdBannersExtension.migrator.migrate
+ end
+ end
+
+ desc "Copies public assets of the Ad Banners to the instance public/ directory."
+ task :update => :environment do
+ is_svn_or_dir = proc {|path| path =~ /\.svn/ || File.directory?(path) }
+ puts "Copying assets from AdBannersExtension"
+ Dir[AdBannersExtension.root + "/public/**/*"].reject(&is_svn_or_dir).each do |file|
+ path = file.sub(AdBannersExtension.root, '')
+ directory = File.dirname(path)
+ mkdir_p RAILS_ROOT + directory
+ cp file, RAILS_ROOT + path
+ end
+ end
+ end
+ end
+end
BIN  public/images/admin/ad_banners/ad.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  public/images/admin/ad_banners/new-ad.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 public/javascripts/admin/ad_banners.js
@@ -0,0 +1,31 @@
+document.observe("dom:loaded", function() {
+ if ($('image-thumbnail')) {
+ var box = $('image-thumbnail');
+ if (!box.hasClassName('droppable')) {
+ Droppables.add(box, {
+ accept: 'asset',
+ hoverclass: 'hover',
+ onDrop: function(element) {
+ AdBanner.attach(element, box);
+ }
+ });
+ box.addClassName('droppable');
+ }
+ if (img = box.select('img')[0]) {
+ box.style.width = img.getWidth() + 'px';
+ box.style.height = img.getHeight() + 'px';
+ }
+ Asset.MakeDraggables();
+ }
+});
+
+var AdBanner = {};
+
+AdBanner.attach = function(element, box) {
+ var img = element.select('a.bucket_link img')[0].cloneNode(true);
+ var asset_id = element.id.split('_').last();
+ $('ad_banner_asset_id').value = asset_id;
+ box.update(img);
+ box.style.width = img.getWidth() + 'px';
+ box.style.height = img.getHeight() + 'px';
+}
361 public/javascripts/lowpro.js
@@ -0,0 +1,361 @@
+LowPro = {};
+LowPro.Version = '0.5';
+LowPro.CompatibleWithPrototype = '1.6';
+
+if (Prototype.Version.indexOf(LowPro.CompatibleWithPrototype) != 0 && window.console && window.console.warn)
+ console.warn("This version of Low Pro is tested with Prototype " + LowPro.CompatibleWithPrototype +
+ " it may not work as expected with this version (" + Prototype.Version + ")");
+
+if (!Element.addMethods)
+ Element.addMethods = function(o) { Object.extend(Element.Methods, o); };
+
+// Simple utility methods for working with the DOM
+DOM = {};
+
+// DOMBuilder for prototype
+DOM.Builder = {
+ tagFunc : function(tag) {
+ return function() {
+ var attrs, children;
+ if (arguments.length>0) {
+ if (arguments[0].constructor == Object) {
+ attrs = arguments[0];
+ children = Array.prototype.slice.call(arguments, 1);
+ } else {
+ children = arguments;
+ };
+ children = $A(children).flatten();
+ }
+ return DOM.Builder.create(tag, attrs, children);
+ };
+ },
+ create : function(tag, attrs, children) {
+ attrs = attrs || {}; children = children || []; tag = tag.toLowerCase();
+ var el = new Element(tag, attrs);
+
+ for (var i=0; i<children.length; i++) {
+ if (typeof children[i] == 'string')
+ children[i] = document.createTextNode(children[i]);
+ el.appendChild(children[i]);
+ }
+ return $(el);
+ }
+};
+
+// Automatically create node builders as $tagName.
+(function() {
+ var els = ("p|div|span|strong|em|img|table|tr|td|th|thead|tbody|tfoot|pre|code|" +
+ "h1|h2|h3|h4|h5|h6|ul|ol|li|form|input|textarea|legend|fieldset|" +
+ "select|option|blockquote|cite|br|hr|dd|dl|dt|address|a|button|abbr|acronym|" +
+ "script|link|style|bdo|ins|del|object|param|col|colgroup|optgroup|caption|" +
+ "label|dfn|kbd|samp|var").split("|");
+ var el, i=0;
+ while (el = els[i++])
+ window['$' + el] = DOM.Builder.tagFunc(el);
+})();
+
+DOM.Builder.fromHTML = function(html) {
+ var root;
+ if (!(root = arguments.callee._root))
+ root = arguments.callee._root = document.createElement('div');
+ root.innerHTML = html;
+ return root.childNodes[0];
+};
+
+
+
+// Wraps the 1.6 contentloaded event for backwards compatibility
+//
+// Usage:
+//
+// Event.onReady(callbackFunction);
+Object.extend(Event, {
+ onReady : function(f) {
+ if (document.body) f();
+ else document.observe('dom:loaded', f);
+ }
+});
+
+// Based on event:Selectors by Justin Palmer
+// http://encytemedia.com/event-selectors/
+//
+// Usage:
+//
+// Event.addBehavior({
+// "selector:event" : function(event) { /* event handler. this refers to the element. */ },
+// "selector" : function() { /* runs function on dom ready. this refers to the element. */ }
+// ...
+// });
+//
+// Multiple calls will add to exisiting rules. Event.addBehavior.reassignAfterAjax and
+// Event.addBehavior.autoTrigger can be adjusted to needs.
+Event.addBehavior = function(rules) {
+ var ab = this.addBehavior;
+ Object.extend(ab.rules, rules);
+
+ if (!ab.responderApplied) {
+ Ajax.Responders.register({
+ onComplete : function() {
+ if (Event.addBehavior.reassignAfterAjax)
+ setTimeout(function() { ab.reload(); }, 10);
+ }
+ });
+ ab.responderApplied = true;
+ }
+
+ if (ab.autoTrigger) {
+ this.onReady(ab.load.bind(ab, rules));
+ }
+
+};
+
+Event.delegate = function(rules) {
+ return function(e) {
+ var element = $(e.element());
+ for (var selector in rules)
+ if (element.match(selector)) return rules[selector].apply(this, $A(arguments));
+ };
+};
+
+Object.extend(Event.addBehavior, {
+ rules : {}, cache : [],
+ reassignAfterAjax : false,
+ autoTrigger : true,
+
+ load : function(rules) {
+ for (var selector in rules) {
+ var observer = rules[selector];
+ var sels = selector.split(',');
+ sels.each(function(sel) {
+ var parts = sel.split(/:(?=[a-z]+$)/), css = parts[0], event = parts[1];
+ $$(css).each(function(element) {
+ if (event) {
+ observer = Event.addBehavior._wrapObserver(observer);
+ $(element).observe(event, observer);
+ Event.addBehavior.cache.push([element, event, observer]);
+ } else {
+ if (!element.$$assigned || !element.$$assigned.include(observer)) {
+ if (observer.attach) observer.attach(element);
+
+ else observer.call($(element));
+ element.$$assigned = element.$$assigned || [];
+ element.$$assigned.push(observer);
+ }
+ }
+ });
+ });
+ }
+ },
+
+ unload : function() {
+ this.cache.each(function(c) {
+ Event.stopObserving.apply(Event, c);
+ });
+ this.cache = [];
+ },
+
+ reload: function() {
+ var ab = Event.addBehavior;
+ ab.unload();
+ ab.load(ab.rules);
+ },
+
+ _wrapObserver: function(observer) {
+ return function(event) {
+ if (observer.call(this, event) === false) event.stop();
+ };
+ }
+
+});
+
+Event.observe(window, 'unload', Event.addBehavior.unload.bind(Event.addBehavior));
+
+// A silly Prototype style shortcut for the reckless
+$$$ = Event.addBehavior.bind(Event);
+
+// Behaviors can be bound to elements to provide an object orientated way of controlling elements
+// and their behavior. Use Behavior.create() to make a new behavior class then use attach() to
+// glue it to an element. Each element then gets it's own instance of the behavior and any
+// methods called onxxx are bound to the relevent event.
+//
+// Usage:
+//
+// var MyBehavior = Behavior.create({
+// onmouseover : function() { this.element.addClassName('bong') }
+// });
+//
+// Event.addBehavior({ 'a.rollover' : MyBehavior });
+//
+// If you need to pass additional values to initialize use:
+//
+// Event.addBehavior({ 'a.rollover' : MyBehavior(10, { thing : 15 }) })
+//
+// You can also use the attach() method. If you specify extra arguments to attach they get passed to initialize.
+//
+// MyBehavior.attach(el, values, to, init);
+//
+// Finally, the rawest method is using the new constructor normally:
+// var draggable = new Draggable(element, init, vals);
+//
+// Each behaviour has a collection of all its instances in Behavior.instances
+//
+var Behavior = {
+ create: function() {
+ var parent = null, properties = $A(arguments);
+ if (Object.isFunction(properties[0]))
+ parent = properties.shift();
+
+ var behavior = function() {
+ var behavior = arguments.callee;
+ if (!this.initialize) {
+ var args = $A(arguments);
+
+ return function() {
+ var initArgs = [this].concat(args);
+ behavior.attach.apply(behavior, initArgs);
+ };
+ } else {
+ var args = (arguments.length == 2 && arguments[1] instanceof Array) ?
+ arguments[1] : Array.prototype.slice.call(arguments, 1);
+
+ this.element = $(arguments[0]);
+ this.initialize.apply(this, args);
+ behavior._bindEvents(this);
+ behavior.instances.push(this);
+ }
+ };
+
+ Object.extend(behavior, Class.Methods);
+ Object.extend(behavior, Behavior.Methods);
+ behavior.superclass = parent;
+ behavior.subclasses = [];
+ behavior.instances = [];
+
+ if (parent) {
+ var subclass = function() { };
+ subclass.prototype = parent.prototype;
+ behavior.prototype = new subclass;
+ parent.subclasses.push(behavior);
+ }
+
+ for (var i = 0; i < properties.length; i++)
+ behavior.addMethods(properties[i]);
+
+ if (!behavior.prototype.initialize)
+ behavior.prototype.initialize = Prototype.emptyFunction;
+
+ behavior.prototype.constructor = behavior;
+
+ return behavior;
+ },
+ Methods : {
+ attach : function(element) {
+ return new this(element, Array.prototype.slice.call(arguments, 1));
+ },
+ _bindEvents : function(bound) {
+ for (var member in bound)
+ if (member.match(/^on(.+)/) && typeof bound[member] == 'function')
+ bound.element.observe(RegExp.$1, Event.addBehavior._wrapObserver(bound[member].bindAsEventListener(bound)));
+ }
+ }
+};
+
+
+
+Remote = Behavior.create({
+ initialize: function(options) {
+ if (this.element.nodeName == 'FORM') new Remote.Form(this.element, options);
+ else new Remote.Link(this.element, options);
+ }
+});
+
+Remote.Base = {
+ initialize : function(options) {
+ this.options = Object.extend({
+ evaluateScripts : true
+ }, options || {});
+
+ this._bindCallbacks();
+ },
+ _makeRequest : function(options) {
+ if (options.confirm) {
+ if (confirm(options.confirm)) {
+ if (options.update) new Ajax.Updater(options.update, options.url, options);
+ else new Ajax.Request(options.url, options);
+ return false;
+ }
+ } else {
+ if (options.update) new Ajax.Updater(options.update, options.url, options);
+ else new Ajax.Request(options.url, options);
+ return false;
+ }
+ },
+ _bindCallbacks: function() {
+ $w('onCreate onComplete onException onFailure onInteractive onLoading onLoaded onSuccess').each(function(cb) {
+ if (Object.isFunction(this.options[cb]))
+ this.options[cb] = this.options[cb].bind(this);
+ }.bind(this));
+ }
+};
+
+Remote.Link = Behavior.create(Remote.Base, {
+ onclick : function() {
+ var options = Object.extend({ url : this.element.href, method : 'get' }, this.options);
+ return this._makeRequest(options);
+ }
+});
+
+
+Remote.Form = Behavior.create(Remote.Base, {
+ onclick : function(e) {
+ var sourceElement = e.element();
+
+ if (['input', 'button'].include(sourceElement.nodeName.toLowerCase()) &&
+ sourceElement.type.match(/submit|image/))
+ this._submitButton = sourceElement;
+ },
+ onsubmit : function() {
+ var parameters = this.element.serialize();
+
+ if (parameters.blank()) {
+ parameters = this.options.parameters;
+ } else {
+ parameters = parameters + '&' + this.options.parameters;
+ }
+ delete this.options.parameters;
+ if (this._submitButton) {
+ if (parameters.blank()) {
+ parameters = this._submitButton.name + "=" + this._submitButton.value;
+ } else {
+ parameters = parameters + '&' + this._submitButton.name + "=" + this._submitButton.value;
+ }
+ }
+ var options = Object.extend({
+ url : this.element.action,
+ method : this.element.method || 'get',
+ parameters : parameters
+ }, this.options);
+ this._submitButton = null;
+ return this._makeRequest(options);
+ }
+});
+
+Observed = Behavior.create({
+ initialize : function(callback, options) {
+ this.callback = callback.bind(this);
+ this.options = options || {};
+ this.observer = (this.element.nodeName == 'FORM') ? this._observeForm() : this._observeField();
+ },
+ stop: function() {
+ this.observer.stop();
+ },
+ _observeForm: function() {
+ return (this.options.frequency) ? new Form.Observer(this.element, this.options.frequency, this.callback) :
+ new Form.EventObserver(this.element, this.callback);
+ },
+ _observeField: function() {
+ return (this.options.frequency) ? new Form.Element.Observer(this.element, this.options.frequency, this.callback) :
+ new Form.Element.EventObserver(this.element, this.callback);
+ }
+});
+
64 public/stylesheets/admin/ad_banners.css
@@ -0,0 +1,64 @@
+#content #ad_banners.index {
+ border: none;
+ border-collapse: collapse;
+}
+
+#content #ad_banners.index .node .name {
+ font-size: 115%;
+ font-weight: bold;
+}
+
+#content #ad_banners.index .node .name a,
+#content #ad_banners.index .node .name a:visited {
+ color: black;
+ text-decoration: none;
+}
+
+#content #ad_banners.index .node .name a:hover,
+#content #ad_banners.index .node .name a:visited:hover {
+ color: blue;
+ text-decoration: underline;
+}
+
+#content #ad_banner_form_area {
+ width: 40em;
+}
+
+#content #ad_banner_form_area .title {
+ margin-bottom: 1em;
+}
+
+#content .form-area .error-with-field .error {
+ margin-top: -1em;
+}
+
+#content #ad_banner_form_area #ad_banner_name.textbox,
+#content #ad_banner_form_area #ad_banner_link_url.textbox {
+ font-size: 125%;
+ width: 100%;
+}
+
+#content #ad_banner_form_area #image-thumbnail {
+ background-color: #eaeaea;
+ border: 1px solid #ccc;
+ display: block;
+ padding: 3px;
+}
+#content #ad_banner_form_area #image-thumbnail .note {
+ display: block;
+ margin: 0.5em;
+ text-align: center;
+ text-transform: uppercase;
+}
+#content #ad_banner_form_area #image-thumbnail.hover {
+ background-color: #ccc;
+ border: 1px solid #f00;
+}
+#content #ad_banner_form_area #image-thumbnail.hover .note {
+ color: #666;
+}
+
+#content #ad_banner_form_area .note {
+ font-weight: normal;
+ font-size: 90%;
+}
10 spec/controllers/admin/ad_banners_controller_spec.rb
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+describe Admin::AdBannersController do
+
+ ##Delete this example and add some real ones
+ #it "should use Admin::AdBannersController" do
+ # controller.should be_an_instance_of(Admin::AdBannersController)
+ #end
+
+end
11 spec/helpers/admin/ad_banners_helper_spec.rb
@@ -0,0 +1,11 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+describe Admin::AdBannersHelper do
+
+ ##Delete this example and add some real ones or delete this file
+ #it "should include the Admin::AdBannersHelper" do
+ # included_modules = self.metaclass.send :included_modules
+ # included_modules.should include(Admin::AdBannersHelper)
+ #end
+
+end
49 spec/models/ad_banner_spec.rb
@@ -0,0 +1,49 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+
+describe AdBanner do
+
+ before(:each) do
+ asset = Asset.create!(:title => 'Foo',
+ :caption => 'Bar',
+ :asset_file_name => 'asset.jpg',
+ :asset_content_type => 'image/jpeg',
+ :asset_file_size => 12345)
+ @ad_banner = AdBanner.new(:name => 'Advertiser',
+ :asset => asset,
+ :link_url => 'http://www.example.com',
+ :weight => '1',
+ :link_target => '')
+ end
+
+ context 'validations' do
+
+ it "should be valid" do
+ @ad_banner.should be_valid
+ end
+
+ it 'should require a name' do
+ @ad_banner.name = nil
+ @ad_banner.should_not be_valid
+ @ad_banner.errors.on(:name).should == I18n.t('activerecord.errors.messages.blank')
+ end
+
+ it 'should require an asset' do
+ @ad_banner.asset = nil
+ @ad_banner.should_not be_valid
+ @ad_banner.errors.on(:asset_id).should == I18n.t('activerecord.errors.messages.blank')
+ end
+
+ it 'should not require a URL' do
+ @ad_banner.link_url = nil
+ @ad_banner.should be_valid
+ end
+
+ it 'should require a valid URL' do
+ @ad_banner.link_url = 'example.com'
+ @ad_banner.should_not be_valid
+ @ad_banner.errors.on(:link_url).should == "doesn't look like a URL"
+ end
+
+ end
+
+end
6 spec/spec.opts
@@ -0,0 +1,6 @@
+--colour
+--format
+progress
+--loadby
+mtime
+--reverse
36 spec/spec_helper.rb
@@ -0,0 +1,36 @@
+unless defined? RADIANT_ROOT
+ ENV["RAILS_ENV"] = "test"
+ case
+ when ENV["RADIANT_ENV_FILE"]
+ require ENV["RADIANT_ENV_FILE"]
+ when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../../")}/config/environment"
+ else
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../")}/config/environment"
+ end
+end
+require "#{RADIANT_ROOT}/spec/spec_helper"
+
+Dataset::Resolver.default << (File.dirname(__FILE__) + "/datasets")
+
+if File.directory?(File.dirname(__FILE__) + "/matchers")
+ Dir[File.dirname(__FILE__) + "/matchers/*.rb"].each {|file| require file }
+end
+
+Spec::Runner.configure do |config|
+ # config.use_transactional_fixtures = true
+ # config.use_instantiated_fixtures = false
+ # config.fixture_path = RAILS_ROOT + '/spec/fixtures'
+
+ # You can declare fixtures for each behaviour like this:
+ # describe "...." do
+ # fixtures :table_a, :table_b
+ #
+ # Alternatively, if you prefer to declare them only once, you can
+ # do so here, like so ...
+ #
+ # config.global_fixtures = :table_a, :table_b
+ #
+ # If you declare global fixtures, be aware that they will be declared
+ # for all of your examples, even those that don't use them.
+end
Please sign in to comment.
Something went wrong with that request. Please try again.