From: Jan Provaznik <[email protected]>

Group selection (optional packages support):
When user selects group, all packages from this group are displayed
in the "Selected packages" panel, but only non-optional packages
are checked. XML file uses <groups> node to remember group
selection.

Package selection:
When user selects package in "Packages" tab, package is added into
"Individual packages" group (even if this package is checked in any
other selected group).

Unobtrusive Javascript
Image builder now works without javascript (though there is still one
JS hook for image repository selection field).
---
 src/app/controllers/templates_controller.rb        |   73 ++++++-----
 src/app/helpers/image_descriptor_helper.rb         |    2 -
 src/app/models/image_descriptor.rb                 |   15 ++-
 src/app/util/image_descriptor_xml.rb               |  133 +++++++++++++++-----
 src/app/util/repository_manager.rb                 |   81 ++++++++++--
 src/app/views/templates/_groups.haml               |   11 ++
 src/app/views/templates/_packages.haml             |    6 +
 src/app/views/templates/_selected_packages.haml    |   10 ++
 src/app/views/templates/_targets.haml              |    2 +-
 src/app/views/templates/new.haml                   |    2 +-
 src/app/views/templates/repository_packages.haml   |    4 -
 .../templates/repository_packages_by_group.haml    |    5 -
 src/app/views/templates/services.haml              |    3 +-
 src/app/views/templates/software.haml              |   67 +++++-----
 src/app/views/templates/summary.haml               |   26 ++--
 src/public/stylesheets/components.css              |  122 ++++++++++--------
 16 files changed, 370 insertions(+), 192 deletions(-)
 delete mode 100644 src/app/helpers/image_descriptor_helper.rb
 create mode 100644 src/app/views/templates/_groups.haml
 create mode 100644 src/app/views/templates/_packages.haml
 create mode 100644 src/app/views/templates/_selected_packages.haml
 delete mode 100644 src/app/views/templates/repository_packages.haml
 delete mode 100644 src/app/views/templates/repository_packages_by_group.haml

diff --git a/src/app/controllers/templates_controller.rb 
b/src/app/controllers/templates_controller.rb
index a2243aa..6a06ef4 100644
--- a/src/app/controllers/templates_controller.rb
+++ b/src/app/controllers/templates_controller.rb
@@ -1,13 +1,11 @@
+require 'util/repository_manager'
+
 class TemplatesController < ApplicationController
   layout :layout
   before_filter :require_user, :check_permission
 
   def layout
-    return "aggregator" unless ajax?
-  end
-
-  def ajax?
-    return params[:ajax] == "true"
+    request.xhr? ? false : 'aggregator'
   end
 
   def new
@@ -30,8 +28,23 @@ class TemplatesController < ApplicationController
     if params[:cancel]
       redirect_to :controller => "dashboard", :action => 'index'
     else
-      update_xml
-      @repositories = RepositoryManager.new.repositories
+      @repository_manager = RepositoryManager.new
+      @image_descriptor = params[:id] ? ImageDescriptor.find(params[:id]) : 
ImageDescriptor.new
+      @groups = @repository_manager.all_groups(params[:repository])
+      if params[:tab].to_s == 'packages'
+        @selected_tab = 'packages'
+        @packages = @repository_manager.all_packages(params[:repository])
+      else
+        @selected_tab = 'groups'
+      end
+
+      if request.xhr?
+        render :partial => @selected_tab
+        return
+      end
+
+      @image_descriptor.update_xml_attributes!(params[:xml] || {})
+
       if params[:back]
         redirect_to :action => 'new', :id => params[:id]
         return
@@ -43,8 +56,7 @@ class TemplatesController < ApplicationController
     if params[:cancel]
       redirect_to :controller => "dashboard", :action => 'index'
     else
-      @image_descriptor = params[:id] ? ImageDescriptor.find(params[:id]) : 
ImageDescriptor.new
-      @image_descriptor.update_xml_attributes!(params[:xml] || {})
+      update_xml
       @image_descriptor.complete = true
       @image_descriptor.save!
       @all_targets = ImageDescriptorTarget.available_targets
@@ -61,7 +73,6 @@ class TemplatesController < ApplicationController
     elsif params[:back]
       redirect_to :action => 'software', :id => params[:id]
     else
-      @all_targets = ImageDescriptorTarget.available_targets
       if params[:targets]
         params[:targets].each do |target|
           ImageDescriptorTarget.new_if_not_exists(:name => target, 
:image_descriptor_id => params[:id], :status => 
ImageDescriptorTarget::STATE_QUEUED)
@@ -76,35 +87,35 @@ class TemplatesController < ApplicationController
     @all_targets = ImageDescriptorTarget.available_targets
   end
 
-  def selected_packages
-    data = ImageDescriptor.find(params[:id]).xml.packages
+  def select_group
+    update_group_or_package(:add_group, params[:group])
   end
 
-  def repository_packages
-    @packages = []
-    rmanager = RepositoryManager.new
-    rmanager.repositories.keys.each do |repid|
-      next if params[:repository] and params[:repository] != 'all' and repid 
!= params[:repository]
-      rep = rmanager.get_repository(repid)
-      @packages += rep.get_packages
-    end
+  def remove_group
+    update_group_or_package(:remove_group, params[:group])
   end
 
-  def repository_packages_by_group
-    @packages = {}
-    rmanager = RepositoryManager.new
-    rmanager.repositories.keys.each do |repid|
-      next if params[:repository] and params[:repository] != 'all' and repid 
!= params[:repository]
-      rep = rmanager.get_repository(repid)
-      rep.get_packages_by_group.each do |group, pkgs|
-        @packages[group] ||= []
-        @packages[group] += pkgs
-      end
-    end
+  def select_package
+    update_group_or_package(:add_package, params[:package], params[:group])
   end
 
   private
 
+  def check_permission
+    #require_privilege(Privilege::IMAGE_MODIFY)
+  end
+
+  def update_group_or_package(method, *args)
+    @image_descriptor = params[:id] ? ImageDescriptor.find(params[:id]) : 
ImageDescriptor.new
+    @image_descriptor.xml.send(method, *args)
+    @image_descriptor.save_xml!
+    if request.xhr?
+      render :partial => 'selected_packages'
+    else
+      redirect_to :action => 'software', :id => @image_descriptor
+    end
+  end
+
   def update_xml
     @image_descriptor = params[:id] ? ImageDescriptor.find(params[:id]) : 
ImageDescriptor.new
     @image_descriptor.update_xml_attributes!(params[:xml] || {})
diff --git a/src/app/helpers/image_descriptor_helper.rb 
b/src/app/helpers/image_descriptor_helper.rb
deleted file mode 100644
index fc89001..0000000
--- a/src/app/helpers/image_descriptor_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module ImageDescriptorHelper
-end
diff --git a/src/app/models/image_descriptor.rb 
b/src/app/models/image_descriptor.rb
index 29a3c81..3ce7e25 100644
--- a/src/app/models/image_descriptor.rb
+++ b/src/app/models/image_descriptor.rb
@@ -1,4 +1,3 @@
-require 'util/repository_manager'
 require 'util/image_descriptor_xml'
 
 class ImageDescriptor < ActiveRecord::Base
@@ -7,14 +6,18 @@ class ImageDescriptor < ActiveRecord::Base
   #TODO: validations
 
   def update_xml_attributes!(opts = {})
-    doc = ImageDescriptorXML.new(self[:xml])
+    doc = xml
     doc.name = opts[:name] if opts[:name]
     doc.platform = opts[:platform] if opts[:platform]
     doc.description = opts[:description] if opts[:description]
-    doc.services = opts[:services] if opts[:services]
-    doc.packages = opts[:packages] if opts[:packages]
-    self[:xml] = doc.to_xml
-    @xml = doc
+    doc.services = (opts[:services] || []) if opts[:services] or 
opts[:set_services]
+    doc.packages = (opts[:packages] || []) if opts[:packages] or 
opts[:set_packages]
+    save_xml!
+  end
+
+  def save_xml!
+    self[:xml] = xml.to_xml
+    @xml = nil
     save!
   end
 
diff --git a/src/app/util/image_descriptor_xml.rb 
b/src/app/util/image_descriptor_xml.rb
index c5ed310..860bfa4 100644
--- a/src/app/util/image_descriptor_xml.rb
+++ b/src/app/util/image_descriptor_xml.rb
@@ -18,8 +18,18 @@
 # also available at http://www.gnu.org/copyleft/gpl.html.
 
 require 'yaml'
+require 'util/repository_manager'
 
 class ImageDescriptorXML
+
+  UNKNOWN_GROUP = 'Individual packages'
+
+  # FIXME: temporary way to only add jboss until other
+  # services are supported
+  SERVICE_PACKAGE_GROUPS = {
+    'jboss' => 'JBoss Core Packages'
+  }
+
   def initialize(xmlstr = "")
     @doc = Nokogiri::XML(xmlstr)
     # create at least root node if it doesn't exist
@@ -41,8 +51,7 @@ class ImageDescriptorXML
   def platform=(str)
     # FIXME: we remove all repos beacouse we don't know which one is for
     # platform
-    recreate_repo_nodes(str, services)
-
+    recreate_repo_nodes(str)
     node = get_or_create_node('os')
     node.content = str
   end
@@ -70,16 +79,14 @@ class ImageDescriptorXML
   def services=(services)
     service_node = get_or_create_node('services')
     service_node.xpath('.//service').remove
-    repositories = repository_manager.repositories
     services.each do |s|
       snode = Nokogiri::XML::Node.new('service', @doc)
       service_node << snode
       snode.content = s[0]
-      if repo = repositories[s[0]]
-        add_service_packages(s[0], repo)
+      if group = SERVICE_PACKAGE_GROUPS[s[0]]
+        add_group(group)
       end
     end
-    recreate_repo_nodes(platform, services)
     @services = nil
   end
 
@@ -97,28 +104,107 @@ class ImageDescriptorXML
     return @doc.to_xml
   end
 
+  def packages_by_group
+    groups = {}
+    @root.xpath('/image/groups/group').each do |g|
+      groups[g.text] = []
+    end
+    @root.xpath('/image/packages/package').each do |s|
+      name = s.at_xpath('.//group').text
+      group = (groups[name] || groups[UNKNOWN_GROUP] ||= [])
+      group << {:name => s.at_xpath('.//name').text}
+    end
+    return groups
+  end
+
+  def all_packages_by_group
+    groups = {}
+    all_groups = repository_manager.all_groups
+    packages_by_group.each do |group, pkgs|
+      if group_all = all_groups[group]
+        groups[group] ||= []
+        group_all[:packages].keys.sort.each do |pkg|
+          groups[group] << {:name => pkg, :checked => pkgs.find {|p| p[:name] 
== pkg} ? true : false}
+        end
+      else
+        groups[UNKNOWN_GROUP] ||= []
+        groups[UNKNOWN_GROUP] += pkgs.map {|pkg| {:name => pkg[:name], 
:checked => true}}
+      end
+    end
+
+    unknown_group = groups.delete(UNKNOWN_GROUP)
+    sorted = groups.keys.sort.map do |group|
+      {:name => group, :pkgs => groups[group]}
+    end
+    if unknown_group
+      sorted << {:name => UNKNOWN_GROUP, :pkgs => unknown_group}
+    end
+
+    return sorted
+  end
+
   def packages
-    packages = {}
+    packages = []
     @root.xpath('/image/packages/package').each do |s|
-      group = s.at_xpath('.//group').text
-      packages[group] ||= []
-      packages[group] << {:name => s.at_xpath('.//name').text}
+      packages << {:name => s.at_xpath('.//name').text, :group => 
s.at_xpath('.//group').text}
     end
-    packages
+    return packages
   end
 
   def packages=(packages)
     pkgs_node = get_or_create_node('packages')
     pkgs_node.xpath('.//package').remove
-    packages.each do |pkg|
+    packages.uniq.each do |pkg|
       group, name = pkg.split(/#/, 2)
-      add_package(pkgs_node, name, group)
+      add_package_node(pkgs_node, name, group)
+    end
+  end
+
+  def add_package(pkg, group)
+    group ||= UNKNOWN_GROUP
+    pkgs_node = get_or_create_node('packages')
+    unless older = packages.find {|p| p[:name] == pkg and p[:group] == group}
+      add_package_node(pkgs_node, pkg, group)
+    end
+  end
+
+  def add_group(gname)
+    unless group = repository_manager.all_groups[gname]
+      raise "group #{gname} not found in repositories"
+    end
+    groups = packages_by_group
+    unless groups[gname]
+      groups_node = get_or_create_node('groups')
+      add_group_node(groups_node, gname)
+    end
+    group[:packages].each do |p, type|
+      next if type == 'optional'
+      add_package(p, group[:name])
+    end
+  end
+
+  def remove_group(group)
+    groups = packages_by_group
+    groups.delete(group)
+    pkgs_node = get_or_create_node('packages')
+    pkgs_node.xpath('.//package').remove
+    groups_node = get_or_create_node('groups')
+    groups_node.xpath('.//group').remove
+    groups.each do |group, pkgs|
+      pkgs.each { |pkg| add_package_node(pkgs_node, pkg[:name], group) }
+      add_group_node(groups_node, group)
     end
   end
 
   private
 
-  def recreate_repo_nodes(platform, services)
+  def add_group_node(parent, group)
+    n = Nokogiri::XML::Node.new('group', @doc)
+    n.content = group
+    parent << n
+  end
+
+  def recreate_repo_nodes(platform)
     unless repconf = platforms[platform]
       raise "unknown platform #{platform}"
     end
@@ -128,12 +214,9 @@ class ImageDescriptorXML
     rnode = get_or_create_node('repo', repo_node)
     rnode.content = repconf['baseurl']
 
-    repositories = repository_manager.repositories
-    services.each do |s|
-      if rep = repositories[s[0]]
-        rnode = get_or_create_node('repo', repo_node)
-        rnode.content = rep['baseurl']
-      end
+    repository_manager.repositories.each do |rname, repo|
+      rnode = get_or_create_node('repo', repo_node)
+      rnode.content = repo['baseurl']
     end
   end
 
@@ -150,15 +233,7 @@ class ImageDescriptorXML
     return node ? node.text : nil
   end
 
-  def add_service_packages(repid, repconf)
-    repo = repository_manager.get_repository(repid)
-    packages = repo.get_packages
-    pkgs_node = get_or_create_node('packages')
-    pkgs_node.xpath('.//package').remove
-    packages.each {|p| add_package(pkgs_node, p[:name], p[:group])}
-  end
-
-  def add_package(parent, name, group)
+  def add_package_node(parent, name, group)
     pnode = get_or_create_node('package', parent)
     n = Nokogiri::XML::Node.new('name', @doc)
     n.content = name
diff --git a/src/app/util/repository_manager.rb 
b/src/app/util/repository_manager.rb
index 1835390..abb2cee 100644
--- a/src/app/util/repository_manager.rb
+++ b/src/app/util/repository_manager.rb
@@ -22,7 +22,8 @@ require 'yaml'
 
 class RepositoryManager
   class CompsRepository
-    def initialize(baseurl)
+    def initialize(baseurl, id)
+      @id = id
       @baseurl = baseurl
       @repomd_uri = File.join(@baseurl, 'repodata', 'repomd.xml')
       begin
@@ -35,14 +36,13 @@ class RepositoryManager
 
     def get_packages
       packages = []
-      # FIXME: currently are selected only mandatory and default packages,
-      # optional packages are ingored
       get_packages_nodes.each do |node|
         name = node.at_xpath('./xmlns:name/child::text()')
         group = node.at_xpath('./xmlns:format/rpm:group/child::text()')
         description = node.at_xpath('./xmlns:description/child::text()')
         next unless name and group
         packages << {
+          :repository_id => @id,
           :name => name.text,
           :group => group.text,
           :description => description ? description.text : '',
@@ -51,26 +51,55 @@ class RepositoryManager
       return packages
     end
 
-    def get_packages_by_group
+    def get_groups
       groups = {}
-      get_packages.each do |p|
-        group = (groups[p[:group]] ||= [])
-        group << p
+      get_groups_nodes.each do |g|
+        pkgs = get_group_packages(g)
+        next if pkgs.empty?
+        name = g.at_xpath('name').text
+        groups[name] = {
+          :name => name,
+          :description => (t = g.at_xpath('description')) ? t.text : '',
+          :packages => pkgs,
+        }
       end
       return groups
     end
 
     private
 
+    def get_group_packages(group_node)
+      pkgs = {}
+      group_node.xpath('packagelist/packagereq').each do |p|
+        pkgs[p.text] = p.attr('type')
+      end
+      return pkgs
+    end
+
     def get_packages_nodes
       unless @packages_nodes
-        data = get_xml(get_primary_url)
+        data = get_xml(get_url('primary'))
         xml = Nokogiri::XML(data)
         @packages_nodes = xml.xpath('/xmlns:metadata/xmlns:package')
       end
       return @packages_nodes
     end
 
+    def get_groups_nodes
+      unless @groups_nodes
+        # if there is no group definition, group list will be empty
+        begin
+          url = get_url('group')
+        rescue
+          return []
+        end
+        data = get_xml(url)
+        xml = Nokogiri::XML(data)
+        @groups_nodes = xml.xpath('/comps/group')
+      end
+      return @groups_nodes
+    end
+
     def get_xml(url)
       xml_data = open(url)
       if url =~ /\.gz$/
@@ -80,9 +109,9 @@ class RepositoryManager
       end
     end
 
-    def get_primary_url
-      location = 
@repomd.xpath('/xmlns:repomd/xmlns:da...@type="primary"]/xmlns:location').first
-      raise "location for primary data not found" unless location
+    def get_url(type)
+      location = 
@repomd.xpath("/xmlns:repomd/xmlns:da...@type=\"#{type}\"]/xmlns:location").first
+      raise "location for #{type} data not found" unless location
       return File.join(@baseurl, location['href'])
     end
   end
@@ -94,10 +123,38 @@ class RepositoryManager
   def get_repository(repository_id)
     repo = @config[repository_id]
     raise "Repository '#{repository_id}' doesn't exist" unless repo
-    return CompsRepository.new(repo['baseurl'])
+    return CompsRepository.new(repo['baseurl'], repository_id)
   end
 
   def repositories
     return @config
   end
+
+  def all_groups(repository = nil)
+    unless @all_groups
+      @all_groups = {}
+      repositories.keys.each do |r|
+        next if repository and r != 'all' and repository == r
+        get_repository(r).get_groups.each do |group, data|
+          if @all_groups[group]
+            @all_groups[group][:packages].merge!(data[:packages])
+          else
+            @all_groups[group] = data
+          end
+        end
+      end
+    end
+    return @all_groups
+  end
+
+  def all_packages(repository = nil)
+    unless @all_packages
+      @all_packages = []
+      repositories.keys.each do |r|
+        next if repository and r != 'all' and repository == r
+        @all_packages += get_repository(r).get_packages
+      end
+    end
+    return @all_packages
+  end
 end
diff --git a/src/app/views/templates/_groups.haml 
b/src/app/views/templates/_groups.haml
new file mode 100644
index 0000000..46b4521
--- /dev/null
+++ b/src/app/views/templates/_groups.haml
@@ -0,0 +1,11 @@
+%ul{:class => 'groups_to_select'}
+  - @groups.keys.sort.each do |group|
+    %li
+      - pkgs = @groups[group][:packages].keys.join("','")
+      = group
+      %a{:class => 'select_group', :href => url_for(:action => 'select_group', 
:id => params[:id], :group => group)}
+        Select
+      %ul{:class => 'groupkgs'}
+        - @groups[group][:packages].each do |pkg, type|
+          %li
+            = pkg
diff --git a/src/app/views/templates/_packages.haml 
b/src/app/views/templates/_packages.haml
new file mode 100644
index 0000000..2019351
--- /dev/null
+++ b/src/app/views/templates/_packages.haml
@@ -0,0 +1,6 @@
+%ul{:class => 'packages_to_select'}
+  - @packages.each do |pkg|
+    %li
+      = pkg[:name]
+      %a{:class => 'select_package', :href => url_for(:action => 
'select_package', :id => params[:id], :package => pkg[:name], :repository => 
pkg[:repository_id])}
+        Select
diff --git a/src/app/views/templates/_selected_packages.haml 
b/src/app/views/templates/_selected_packages.haml
new file mode 100644
index 0000000..2d86afd
--- /dev/null
+++ b/src/app/views/templates/_selected_packages.haml
@@ -0,0 +1,10 @@
+- @image_descriptor.xml.all_packages_by_group.each do |group|
+  %li{:class => 'group'}
+    = group[:name]
+    %a{:class => 'remove_group', :href => url_for(:action => 'remove_group', 
:id => @image_descriptor, :group => group[:name])}
+      Remove
+  %ul{:class => 'groupkgs'}
+    - group[:pkgs].each do |pkg|
+      %li{:class => 'pkgname'}
+        %input{:type => 'checkbox', :name => 'xml[packages][]', :value => 
group[:name] + '#' + pkg[:name], :checked => pkg[:checked]}
+        = pkg[:name]
diff --git a/src/app/views/templates/_targets.haml 
b/src/app/views/templates/_targets.haml
index 77406f7..1b242e1 100644
--- a/src/app/views/templates/_targets.haml
+++ b/src/app/views/templates/_targets.haml
@@ -8,7 +8,7 @@
           %span{:class => 'actions'}
             &nbsp;
             - if ImageDescriptorTarget::ACTIVE_STATES.include?(target.status)
-              = link_to 'Cancel', {:controller => 'image_descriptor_target', 
:action => 'cancel', :id => target.id, :descriptor_id => @image_descriptor.id}
+              = link_to 'Cancel', {:controller => 'image_descriptor_target', 
:action => 'cancel', :id => target, :descriptor_id => @image_descriptor}
           %span{:class => 'status'}
             = target.status
           = @all_targets[target.name]['name']
diff --git a/src/app/views/templates/new.haml b/src/app/views/templates/new.haml
index 5a1adda..a6e134b 100644
--- a/src/app/views/templates/new.haml
+++ b/src/app/views/templates/new.haml
@@ -3,7 +3,7 @@
 #image_descriptor_form{:class => 'dcloud_form'}
   = error_messages_for 'image_descriptor'
 
-  - form_for @image_descriptor, :url => {:action => 'services', :id => 
@image_descriptor.id} do
+  - form_for @image_descriptor, :url => {:action => 'services', :id => 
@image_descriptor} do
     %ul
       %li
         %label Name
diff --git a/src/app/views/templates/repository_packages.haml 
b/src/app/views/templates/repository_packages.haml
deleted file mode 100644
index 9414b85..0000000
--- a/src/app/views/templates/repository_packages.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-%ul
-  - @packages.each do |pkg|
-    %li
-      = "#{pkg[:name]}<span class='select' onclick=\"select_group({group: 
'#{pkg[:group]}', pkgs: ['#{pkg[:name]}']})\">Select</span>"
diff --git a/src/app/views/templates/repository_packages_by_group.haml 
b/src/app/views/templates/repository_packages_by_group.haml
deleted file mode 100644
index 2f2b704..0000000
--- a/src/app/views/templates/repository_packages_by_group.haml
+++ /dev/null
@@ -1,5 +0,0 @@
-%ul
-  - @packages.keys.map {|group| group.to_s}.sort.each do |group|
-    %li
-      - pkgs = @packages[group].map {|p| p[:name]}.join("','")
-      = "#{group}<span class='select' onclick=\"select_group({group: 
'#{group}', pkgs: ['#{pkgs}']})\">Select</span>"
diff --git a/src/app/views/templates/services.haml 
b/src/app/views/templates/services.haml
index fe91da0..c6692a4 100644
--- a/src/app/views/templates/services.haml
+++ b/src/app/views/templates/services.haml
@@ -3,8 +3,9 @@
 #image_descriptor_form{:class => 'dcloud_form'}
   = error_messages_for 'image_descriptor'
 
-  - form_for @image_descriptor, :url => {:action => 'software', :id => 
@image_descriptor.id} do
+  - form_for @image_descriptor, :url => {:action => 'software', :id => 
@image_descriptor} do
     = hidden_field :image_descriptor, :id
+    = hidden_field_tag 'xml[set_services]', true
     %ul
       %li
         %label Storage
diff --git a/src/app/views/templates/software.haml 
b/src/app/views/templates/software.haml
index 04e34a3..75c207b 100644
--- a/src/app/views/templates/software.haml
+++ b/src/app/views/templates/software.haml
@@ -1,19 +1,33 @@
 - content_for :scripts do
   :javascript
     $(document).ready(function() {
-      $("#dashboard-tabs > ul > li > a").each(function(index) {
-        var link = $(this).attr("href") + "?ajax=true";
-        $(this).attr("href", link);
+      // FIXME: don't know, how to disable first ajax load.
+      // On ajax load, new data are only appended to existing content
+      // workaround is to delete existing content before load
+      $(".groups_to_select").remove();
+      $("#dashboard-tabs").tabs({
+        spinner: "Loading...",
+        load: function(event, ui) {
+          $("a.select_group").click(function(ev) {
+            $("#selected_packages").load($(ev.target).attr('href'), 
loadCallback);
+            return false;
+          });
+          $("a.select_package").click(function(ev) {
+            $("#selected_packages").load($(ev.target).attr('href'), 
loadCallback);
+            return false;
+          });
+        }
       });
-      $("#dashboard-tabs").tabs({spinner: "Loading..."});
-
       // when JS is enabled, hide the Dashboard content -- it'll be loaded
       // by jquery UI tab using ajax
       $("#dashboard-content").hide();
-      #...@image_descriptor.xml.packages.map {|group, pkgs| 
"select_group({group: '#{group}', pkgs: ['#{pkgs.map {|p| 
p[:name]}.join("','")}']});"}.join("\n")}
+      loadCallback();
     });
-    function remove_group(ev) {
-      $(ev.target).parent().remove();
+    function loadCallback() {
+      $("a.remove_group").click(function(ev) {
+        $("#selected_packages").load($(ev.target).attr('href'), loadCallback);
+        return false;
+      });
     };
     function get_repository(ev) {
       var rep = $(ev.target).val();
@@ -28,50 +42,35 @@
       });
       $("#dashboard-tabs").tabs('load', $("#dashboard-tabs").tabs('option', 
'selected'));
     };
-    function select_package(group, pkgname, parent) {
-      if (!parent) parent = $("#selected_packages");
-      if (parent.find(".pkgname").filter(function() {return $(this).text() == 
pkgname}).length == 0) {
-        var node = $("<li><input type=checkbox name='xml[packages][]' value='" 
+ group + '#' + pkgname + "' checked=true><span class='pkgname'>" + pkgname + 
"</span></li>").appendTo(parent);
-      }
-    };
-    function add_group(name) {
-      var group = $("<li><span class='group'>" + name + "</span><span 
class='select' 
onclick='remove_group(event)'>Remove</span><ul></ul></li>").appendTo("#selected_packages");
-      return group.find("ul");
-    };
-    function select_group(opts) {
-      var groups = $("#selected_packages li .group").filter(function() {return 
$(this).text() == opts.group});
-      //FIXME: if group exists, we abort adding group again, merging is not 
supported yet
-      if (groups.length > 0) return;
-      var group = add_group(opts.group);
-      for (var i = 0; i < opts.pkgs.length; i++) {
-        select_package(opts.group, opts.pkgs[i], group);
-      }
-    };
 
-- menu = [ { :text => "Groups", :action => "repository_packages_by_group" }, |
-  { :text => "Packages", :action => "repository_packages" }, ] |
+
+- menu = [ { :text => "Groups", :tab => "groups" }, |
+  { :text => "Packages", :tab => "packages" }, ] |
 
 = render :partial => "nav"
 
 #image_descriptor_form{:class => 'dcloud_form'}
   = error_messages_for 'image_descriptor'
 
-  - form_for @image_descriptor, :url => {:action => 'summary', :id => 
@image_descriptor.id} do
+  - form_for @image_descriptor, :url => {:action => 'summary', :id => 
@image_descriptor} do
+    = hidden_field_tag 'xml[set_packages]', true
     .pkglist_wrapper
       .pkglist
         #dashboard-tabs{ :class => "ui-tabs ui-widget ui-widget-content 
ui-corner-all" }
           %ul{ :class => "ui-tabs ui-widget ui-widget-content ui-corner-all 
ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header" }
             - menu.each do |item|
-              - selection_style = "ui-tabs-selected ui-state-active" if 
item[:action] == controller.action_name
+              - selection_style = "ui-tabs-selected ui-state-active" if 
item[:tab] == @selected_tab
               %li{ :class => "#{selection_style} ui-state-default 
ui-corner-top", :style => 'clear: none' }
-                = "<a href=\"#{url_for :controller => "templates", :action => 
item[:action] }\"><span>#{item[:text]}</span></a>"
+                = "<a href=\"#{url_for(:action => "software", :id => 
@image_descriptor, :tab => item[:tab])}\"><span>#{item[:text]}</span></a>"
             %li{ :class => "select_repository"}
-              = select_tag("repository", ["<option value='all' 
selected='selected'>All</option>"] + @repositories.map{|repid, rep| "<option 
value=\"#{repid}\">#{rep['name']}</option>"}, {:onchange => 
"get_repository(event)"})
+              = select_tag("repository", ["<option value='all' 
selected='selected'>All</option>"] + 
@repository_manager.repositories.map{|repid, rep| "<option 
value=\"#{repid}\">#{rep['name']}</option>"}, {:onchange => 
"get_repository(event)"})
+          - unless request.xhr?
+            = render :partial => @selected_tab
       .pkglist{:style => "margin-left: 30px", :class => "left-pkglist"}
         %div{:class => "ui-tabs ui-widget ui-widget-content ui-corner-all 
ui-helper-reset ui-helper-clearfix ui-widget-header"}
           Selected packages
         %ul{:id => "selected_packages"}
-
+          = render :partial => 'selected_packages'
     .submit
       = submit_tag "Cancel", :name => "cancel"
       = submit_tag "Back", :name => "back"
diff --git a/src/app/views/templates/summary.haml 
b/src/app/views/templates/summary.haml
index faff8e3..d32426d 100644
--- a/src/app/views/templates/summary.haml
+++ b/src/app/views/templates/summary.haml
@@ -2,25 +2,27 @@
   :javascript
     $(document).ready(function() {
       var refreshId = setInterval(function() {
-        $('#image_target_list').load('#{url_for :controller => 'templates', 
:action => 'targets', :id => @image_descriptor.id, :ajax => true}');
+        $('#image_target_list').load('#{url_for :controller => 'templates', 
:action => 'targets', :id => @image_descriptor}');
       }, 10000);
+      $(".expand_target_list_button").css('display', 'block');
+      $("#image_build_form").css('display', 'none');
+      $(".expand_target_list_button").toggle(
+        function(ev) {
+          $("#image_build_form").css('display', 'block');
+          $(this).val('Cancel');
+        }, function(ev) {
+          $("#image_build_form").css('display', 'none');
+          $(this).val('Build Images');
+        }
+      );
     });
-    function toggle_build_form_visibility(ev) {
-      if ($("#image_build_form").css('display') == 'none') {
-        $("#image_build_form").css('display', 'block');
-        $(ev.target).val('Cancel');
-      } else {
-        $("#image_build_form").css('display', 'none');
-        $(ev.target).val('Build Images');
-      }
-    }
 
 = render :partial => "nav"
 
 #image_descriptor_form{:class => 'dcloud_form'}
   = error_messages_for 'image_descriptor'
 
-  - form_for @image_descriptor, :url => {:action => 'build', :id => 
@image_descriptor.id} do
+  - form_for @image_descriptor, :url => {:action => 'build', :id => 
@image_descriptor} do
     .summary_panel
       %ul{:class => 'side_panel'}
         %li
@@ -44,7 +46,7 @@
         .image_build_panel
           %h3
             Images
-            %input{:type => 'button', :value => "Build Images", :style => 
'float:right', :onclick => 'toggle_build_form_visibility(event)'}
+            %input{:type => 'button', :class => "expand_target_list_button", 
:value => "Build Images"}
           #image_build_form
             %ul
               %li
diff --git a/src/public/stylesheets/components.css 
b/src/public/stylesheets/components.css
index e47b9f0..eaf0b81 100644
--- a/src/public/stylesheets/components.css
+++ b/src/public/stylesheets/components.css
@@ -337,6 +337,49 @@ ul.nav li:hover {
   margin-bottom: 15px;
 }
 
+#image_descriptor_nav {
+  margin: 2px 0px 0 0px;
+  width: 98%;
+}
+
+#image_descriptor_nav h2 {
+  height : 29px;
+  font-weight : normal;
+  padding : 10px 10px 0px 10px;
+  margin : 0px;
+}
+
+#image_descriptor_nav ul {
+  margin : 20px;
+  width: 100%;
+}
+
+#image_descriptor_nav ul li {
+  display: inline;
+  float: left;
+  width: 200px;
+  border: 1px solid #ccc;
+  border-top : none;
+  border-right : none;
+  padding-left: 10px;
+  clear: none;
+  font-size: 1em;
+}
+
+#image_descriptor_nav ul li h4 {
+  margin-top : 0px;
+  margin-bottom : 0.5em;
+}
+
+#image_descriptor_nav ul li p {
+  margin-top : 0;
+  margin-bottom : 2em;
+  color : #808080;
+}
+
+#image_descriptor_nav ul li.selected p {
+  color : #000;
+}
 
 #image_descriptor_form {
   width: 800px;
@@ -366,9 +409,11 @@ ul.nav li:hover {
   -moz-border-radius : 6px;
 }
 
-#image_descriptor_form .pkglist .select {
+#image_descriptor_form .pkglist .select_package,
+#image_descriptor_form .pkglist .select_group,
+#image_descriptor_form .pkglist .remove_group {
   float:right;
-  width: 50px !important;
+  width: 60px !important;
   cursor: pointer;
   color: blue;
 }
@@ -386,7 +431,7 @@ ul.nav li:hover {
   width:800px;
 }
 
-#image_descriptor_form .pkglist ul {
+#image_descriptor_form .pkglist ul.ui-tabs-nav {
   background-color: white;
   width : 373px;
   height : 44px;
@@ -397,41 +442,8 @@ ul.nav li:hover {
   margin-top : 5px;
 }
 
-#image_descriptor_nav {
-  margin: 2px 0px 0 0px;
-  width: 98%;
-}
-
-#image_descriptor_nav ul {
-  margin : 20px;
-  width: 100%;
-}
-
-#image_descriptor_nav ul li {
-  display: inline;
-  float: left;
-  width: 200px;
-  border: 1px solid #ccc;
-  border-top : none;
-  border-right : none;
-  padding-left: 10px;
-  clear: none;
-  font-size: 1em;
-}
-
-#image_descriptor_nav ul li h4 {
-  margin-top : 0px;
-  margin-bottom : 0.5em;
-}
-
-#image_descriptor_nav ul li p {
-  margin-top : 0;
-  margin-bottom : 2em;
-  color : #808080;
-}
-
-#image_descriptor_nav ul li.selected p {
-  color : #000;
+#image_descriptor_form .groupkgs li {
+  padding-left: 20px;
 }
 
 #image_descriptor_form .side_panel label {
@@ -440,10 +452,6 @@ ul.nav li:hover {
   padding: 5px;
 }
 
-#image_descriptor_form .submit {
-  padding-top: 20px;
-}
-
 #image_descriptor_form .side_panel {
   width: 150px;
   float: left;
@@ -453,6 +461,10 @@ ul.nav li:hover {
   min-height: 300px;
 }
 
+#image_descriptor_form .submit {
+  padding-top: 20px;
+}
+
 #image_descriptor_form .main_panel {
   width: 580px;
   float: left;
@@ -471,10 +483,6 @@ ul.nav li:hover {
   font-size: 1em;
 }
 
-#image_build_form {
-  display: none;
-}
-
 #image_descriptor_form .side_panel li {
   padding-bottom: 20px;
 }
@@ -485,6 +493,10 @@ ul.nav li:hover {
   padding: 5px;
 }
 
+#image_descriptor_form .groups_to_select > li {
+  padding-bottom: 10px;
+}
+
 #image_descriptor_form .target_list li .status {
   float: right;
   width: 100px;
@@ -500,15 +512,13 @@ ul.nav li:hover {
   padding: 30px;
 }
 
-/* mfojtik's hacks ;-) */
-
-#image_descriptor_nav h2 {
-  height : 29px;
-  font-weight : normal;
-  padding : 10px 10px 0px 10px;
-  margin : 0px;
+#image_descriptor_form .expand_target_list_button {
+  float: right;
+  display: none;
 }
 
+/* mfojtik's hacks ;-) */
+
 #image_descriptor_form.dcloud_form {
   background : none;
   border : none;
@@ -533,7 +543,6 @@ ul.nav li:hover {
 #image_descriptor_form.dcloud_form span {
   float : left;
   width : 300px;
-  color : #808080;
   font-size : 95%;
   padding-left : 1em;
   padding-bottom: 10px;
@@ -561,6 +570,11 @@ ul.nav li:hover {
   float : left;
 }
 
+#selected_packages > li {
+  padding-top: 10px;
+  padding-left: 10px;
+}
+
 #image_descriptor_form.dcloud_form textarea {
   width : 308px;
 }
-- 
1.7.0.1

_______________________________________________
deltacloud-devel mailing list
[email protected]
https://fedorahosted.org/mailman/listinfo/deltacloud-devel

Reply via email to