spiderfw 0.5.10 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. data/CHANGELOG +6 -0
  2. data/Rakefile +4 -1
  3. data/VERSION +1 -0
  4. data/apps/core/auth/controllers/login_controller.rb +10 -1
  5. data/apps/core/auth/controllers/mixins/auth_helper.rb +4 -2
  6. data/apps/core/auth/views/login.shtml +1 -1
  7. data/apps/core/components/_init.rb +1 -1
  8. data/apps/core/components/public/css/spider.css +0 -1
  9. data/apps/core/components/public/js/jquery/plugins/jquery.form.js +2 -1
  10. data/apps/core/components/public/js/list.js +14 -126
  11. data/apps/core/components/public/js/plugins/plugin.js +7 -0
  12. data/apps/core/components/public/js/plugins/sortable.js +124 -0
  13. data/apps/core/components/public/js/spider.js +212 -51
  14. data/apps/core/components/widgets/list/list.shtml +1 -0
  15. data/apps/core/components/widgets/table/table.rb +1 -1
  16. data/apps/core/components/widgets/table/table.shtml +3 -3
  17. data/apps/core/components/widgets/tabs/tabs.rb +70 -22
  18. data/apps/core/components/widgets/tabs/tabs.shtml +8 -2
  19. data/apps/core/forms/widgets/inputs/file_input/file_input.rb +4 -1
  20. data/data/locale/it/LC_MESSAGES/cms.mo +0 -0
  21. data/data/locale/it/LC_MESSAGES/spider.mo +0 -0
  22. data/data/locale/it/LC_MESSAGES/spider_files.mo +0 -0
  23. data/data/locale/it/LC_MESSAGES/spider_images.mo +0 -0
  24. data/lib/spiderfw.rb +3 -1
  25. data/lib/spiderfw/config/configuration.rb +3 -1
  26. data/lib/spiderfw/controller/controller.rb +8 -0
  27. data/lib/spiderfw/controller/mixins/static_content.rb +14 -2
  28. data/lib/spiderfw/controller/mixins/visual.rb +25 -25
  29. data/lib/spiderfw/controller/page_controller.rb +3 -0
  30. data/lib/spiderfw/controller/request.rb +4 -1
  31. data/lib/spiderfw/controller/session.rb +7 -6
  32. data/lib/spiderfw/create.rb +10 -1
  33. data/lib/spiderfw/model/base_model.rb +104 -57
  34. data/lib/spiderfw/model/condition.rb +9 -1
  35. data/lib/spiderfw/model/data_type.rb +15 -0
  36. data/lib/spiderfw/model/datatypes/uuid.rb +5 -0
  37. data/lib/spiderfw/model/extended_models/managed.rb +2 -2
  38. data/lib/spiderfw/model/mappers/db_mapper.rb +46 -21
  39. data/lib/spiderfw/model/mappers/mapper.rb +34 -8
  40. data/lib/spiderfw/model/mixins/list.rb +2 -0
  41. data/lib/spiderfw/model/mixins/tree.rb +2 -1
  42. data/lib/spiderfw/model/mixins/versioned.rb +12 -9
  43. data/lib/spiderfw/model/model.rb +72 -0
  44. data/lib/spiderfw/model/query_set.rb +7 -0
  45. data/lib/spiderfw/model/storage/base_storage.rb +5 -1
  46. data/lib/spiderfw/model/storage/db/adapters/mysql.rb +5 -2
  47. data/lib/spiderfw/model/storage/db/adapters/oci8.rb +9 -3
  48. data/lib/spiderfw/model/storage/db/db_storage.rb +8 -5
  49. data/lib/spiderfw/model/sync.rb +12 -6
  50. data/lib/spiderfw/requires.rb +1 -0
  51. data/lib/spiderfw/templates/blocks/parent_context.rb +26 -0
  52. data/lib/spiderfw/templates/blocks/widget.rb +17 -7
  53. data/lib/spiderfw/templates/layout.rb +11 -1
  54. data/lib/spiderfw/templates/template.rb +36 -3
  55. data/lib/spiderfw/templates/template_blocks.rb +36 -26
  56. data/lib/spiderfw/utils/annotations.rb +2 -1
  57. data/lib/spiderfw/utils/thread_out.rb +15 -0
  58. data/lib/spiderfw/widget/widget.rb +58 -16
  59. data/spider.gemspec +3 -0
  60. metadata +143 -48
data/CHANGELOG ADDED
@@ -0,0 +1,6 @@
1
+ = 0.5.11
2
+ === 15 June, 2010
3
+ * Many bugfixes
4
+ * Javascript api enhancements, JS plugins
5
+ * Minor Visual refactoring
6
+ * Autogenerated DataTypes
data/Rakefile CHANGED
@@ -5,7 +5,10 @@ require 'pathname'
5
5
  def check_app_path(full, partial)
6
6
  p = Pathname.new(full)
7
7
  rel = p.relative_path_from(Pathname.new(Spider.paths[:core_apps]))
8
- return rel.to_s == partial
8
+ return true if rel.to_s == partial
9
+ rel = p.relative_path_from(Pathname.new(Spider.paths[:apps]))
10
+ return true if rel.to_s == partial
11
+ return false
9
12
  end
10
13
 
11
14
  desc "Update pot/po files. To update a single app, call rake updatepo[app_relative_path], where app_relative_path is the path relative to the apps folder (or 'spider')."
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.11
@@ -10,6 +10,10 @@ module Spider; module Auth
10
10
  nil
11
11
  end
12
12
 
13
+ def self.logout_redirect
14
+ nil
15
+ end
16
+
13
17
  def self.users=(val)
14
18
  @user_classes = val
15
19
  end
@@ -85,7 +89,12 @@ module Spider; module Auth
85
89
  def logout
86
90
  @request.session[:auth] = nil
87
91
  @scene.did_logout = true
88
- render('login')
92
+ red = self.class.logout_redirect
93
+ if red
94
+ redirect(red)
95
+ else
96
+ redirect('index')
97
+ end
89
98
  end
90
99
 
91
100
  end
@@ -11,7 +11,9 @@ module Spider; module Auth
11
11
 
12
12
  def before(action='', *arguments)
13
13
  @request.extend(RequestMethods)
14
- return super if action.index(Spider::Auth.route_url) == 0
14
+ super
15
+ return if action.index(Spider::Auth.route_url) == 0
16
+ return if respond_to?(:serving_static?) && serving_static?
15
17
  self.class.auth_require_users.each do |req|
16
18
  klasses, params = req
17
19
  klasses = [klasses] unless klasses.is_a?(Array)
@@ -64,7 +66,7 @@ module Spider; module Auth
64
66
  end
65
67
  @request.user = user
66
68
  end
67
- super
69
+
68
70
  end
69
71
 
70
72
  def try_rescue(exc)
@@ -1,4 +1,4 @@
1
- <div>
1
+ <div class="login-page">
2
2
  <div class='error' sp:if='@failed_login'>
3
3
  Login errata
4
4
  </div>
@@ -16,7 +16,7 @@ require 'apps/core/components/widgets/crud/crud'
16
16
  require 'apps/core/components/widgets/menu/menu'
17
17
  require 'apps/core/components/widgets/admin/admin'
18
18
  require 'apps/core/components/widgets/confirm/confirm'
19
- # require 'apps/core/components/widgets/tabs/tabs'
19
+ require 'apps/core/components/widgets/tabs/tabs'
20
20
  require 'apps/core/components/widgets/list/list'
21
21
  require 'apps/core/components/widgets/switcher/switcher'
22
22
  require 'apps/core/components/widgets/month_calendar/month_calendar'
@@ -1,5 +1,4 @@
1
1
  .loading-empty{
2
- height: 40px;
3
2
  background: url(img/ajax-loader.gif) no-repeat 10px 10px;
4
3
  }
5
4
 
@@ -373,8 +373,9 @@ $.fn.ajaxSubmit = function(options) {
373
373
  * the form itself.
374
374
  */
375
375
  $.fn.ajaxForm = function(options) {
376
- return this.ajaxFormUnbind().bind('submit.form-plugin', function() {
376
+ return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
377
377
  $(this).ajaxSubmit(options);
378
+ e.stopPropagation();
378
379
  return false;
379
380
  }).bind('click.form-plugin', function(e) {
380
381
  var $el = $(e.target);
@@ -1,6 +1,8 @@
1
1
  Spider.defineWidget('Spider.Components.List', {
2
2
 
3
3
  autoInit: '.wdgt-Spider-Components-List',
4
+
5
+ includePlugins: [Spider.Sortable],
4
6
 
5
7
  startup: function(){
6
8
  },
@@ -12,7 +14,16 @@ Spider.defineWidget('Spider.Components.List', {
12
14
  this.listTagName = this.listEl.get(0).tagName;
13
15
  if (this.el.hasClass('collapsed')) options.collapsed = true;
14
16
  if (this.el.hasClass('tree')) $(this.listTagName, this.el).treeview(options);
15
- if (this.el.hasClass('sortable')) this.makeSortable();
17
+ if (this.el.hasClass('sortable')){
18
+ this.plugin(Spider.Sortable);
19
+ this.makeSortable({
20
+ listEl: this.listEl,
21
+ handle: '> span.desc',
22
+ helper: function(e,item) {
23
+ return $("<div class='treeview-helper'>"+item.find("span.desc").html()+"</div>");
24
+ }
25
+ });
26
+ }
16
27
 
17
28
  };
18
29
  this.ajaxify($('form, .paginator a', this.el));
@@ -22,141 +33,18 @@ Spider.defineWidget('Spider.Components.List', {
22
33
  update: function(){
23
34
  },
24
35
 
25
-
26
- makeSortable: function(){
27
- var options = {
28
- items: '>li',
29
- helper: function(e,item) {
30
- return $("<div class='treeview-helper'>"+item.find("span.desc").html()+"</div>");
31
- },
32
- handle: '> span.desc',
33
- update: this.handleSort.bind(this),
34
- receive: this.handleReceive.bind(this)
35
- };
36
- if (this.el.hasClass('tree')){
37
- options = $.extend(options, {
38
- //revert: true,
39
- sortIndication: {
40
- down: function(item) {
41
- item.before($('<li id="list-sort-indicator" />'));
42
- },
43
- up: function(item) {
44
- item.after($('<li id="list-sort-indicator" />'));
45
- },
46
- remove: function(item) {
47
- $('#list-sort-indicator').remove();
48
- }
49
- },
50
- start: function(e, ui) {
51
- console.log("Tree start:");
52
- console.log(e);
53
- console.log(ui);
54
- // ui.instance.element.treeview({update: ui.item});
55
- },
56
- update: this.handleTreeUpdate.bind(this)
57
- });
58
- this.listEl.sortableTree(options);
59
- // handles drops on non-subtrees nodes
60
- // debugger;
61
- $('.desc', this.el).droppable({
62
- accept: "li",
63
- hoverClass: "drop",
64
- tolerance: "pointer",
65
- // greedy: true,
66
- drop: this.handleTreeDrop.bind(this)
67
- // over: function(e,ui) {
68
- // ui.helper.css("outline", "1px dotted green");
69
- // },
70
- // out: function(e,ui) {
71
- // ui.helper.css("outline", "1px dotted red");
72
- // }
73
- });
74
- }
75
- else{
76
- this.listEl.sortable(options);
77
- }
78
- },
79
-
80
- handleSort: function(e, ui){
81
- if (ui.sender) return; // handled by handleReceive
82
- var item = ui.item;
83
- var pos = this.findLiPosition(item);
84
- if (pos == -1) return;
85
- if (this.listEl.data('sortable').fromOutside){ // hack to work around strange jquery ui behaviour...
86
- return this.acceptFromSender(null, ui.item, pos);
87
- }
88
- this.remote('sort', this.getSortItemId(item), pos);
89
- },
90
-
91
-
92
- handleReceive: function(e, ui){
93
- if (ui.sender == ui.item){
94
- // the item is received from a draggable, not from a list. For some reason the receiver is not
95
- // yet ready to find the position; will call acceptFromSender from handleSort.
96
- return;
97
- }
98
- var pos = this.findLiPosition(ui.item);
99
- return this.acceptFromSender(ui.sender, ui.item, pos);
100
- },
101
-
102
- handleTreeUpdate: function(e, ui){
103
- var parentId = this.getItemId(ui.item.parents('li.tree').eq(0));
104
- var prevId = this.getItemId(ui.item.prev('li.tree'));
105
- this.remote('tree_sort', this.getItemId(ui.item), parentId, prevId);
106
- },
107
-
108
- handleTreeDrop: function(e, ui){
109
- if (e.target.parentNode == ui.draggable[0]) return false; //dropped over itself
110
- console.log('dropped inside');
111
- var dropLi = $(e.target.parentNode);
112
- var subUl = $("> "+this.listTagName, dropLi);
113
- if (subUl.length == 0){
114
- subUl = $("<"+this.listTagName+" />").appendTo(dropLi);
115
- this.el.treeview({add: e.target.parentNode});
116
- }
117
- subUl.append(ui.draggable);
118
- var parentId = this.getItemId($(e.target).parents('li.tree').eq(0));
119
- var prevId = null;
120
- this.remote('tree_sort', this.getItemId(ui.draggable), parentId, prevId);
121
- return false;
122
- },
123
-
124
- acceptFromSender: function(sender, item, pos){
125
- console.error("Accept from sender must be implemented by the widget instance");
126
- },
127
-
128
- getItemId: function(li){
129
- return $('> .dataobject-key', li).text();
130
- },
131
-
132
36
  getItemById: function(id){
133
37
  var found = null;
134
38
  var self = this;
135
39
  $('>li', this.listEl).each(function(){
136
- if (self.getItemId(this) == id){
40
+ $this = $(this);
41
+ if ($this.dataObjectKey == id){
137
42
  found = $(this);
138
43
  return false;
139
44
  }
140
45
  });
141
46
  return found;
142
47
  },
143
-
144
- getSortItemId: function(li){
145
- var k = $('> .sort-key', li);
146
- if (k.length > 0) return k.text();
147
- return this.getItemId(li);
148
- },
149
-
150
- findLiPosition: function(item){
151
- var cnt = 1;
152
- var li = $('> li', this.listEl);
153
- li.each(function(){
154
- if (this == item.get(0)) return false;
155
- cnt++;
156
- });
157
- if (cnt > li.length) return -1; // the row was dropped outside
158
- return cnt;
159
- },
160
48
 
161
49
  sortResult: function(res){
162
50
  console.log(res);
@@ -0,0 +1,7 @@
1
+ Spider.Plugin = Class.extend({
2
+
3
+ init: function(widget){
4
+ this.widget = widget;
5
+ }
6
+
7
+ });
@@ -0,0 +1,124 @@
1
+ Spider.Sortable = Spider.Plugin.extend({
2
+
3
+
4
+ makeSortable: function(options){
5
+ var options = $.extend({
6
+ listSelector: 'ul',
7
+ items: '>li',
8
+ update: this.handleSort.bind(this),
9
+ receive: this.handleReceive.bind(this)
10
+ }, options);
11
+ this.listEl = options.listEl;
12
+ if (!this.listEl) this.listEl = $(options.listSelector, this.el);
13
+ if (this.el.hasClass('tree')){
14
+ options = $.extend(options, {
15
+ //revert: true,
16
+ sortIndication: {
17
+ down: function(item) {
18
+ item.before($('<li id="list-sort-indicator" />'));
19
+ },
20
+ up: function(item) {
21
+ item.after($('<li id="list-sort-indicator" />'));
22
+ },
23
+ remove: function(item) {
24
+ $('#list-sort-indicator').remove();
25
+ }
26
+ },
27
+ start: function(e, ui) {
28
+ console.log("Tree start:");
29
+ console.log(e);
30
+ console.log(ui);
31
+ // ui.instance.element.treeview({update: ui.item});
32
+ },
33
+ update: this.handleTreeUpdate.bind(this)
34
+ });
35
+ this.listEl.sortableTree(options);
36
+ // handles drops on non-subtrees nodes
37
+ // debugger;
38
+ $('.desc', this.el).droppable({
39
+ accept: "li",
40
+ hoverClass: "drop",
41
+ tolerance: "pointer",
42
+ // greedy: true,
43
+ drop: this.handleTreeDrop.bind(this)
44
+ // over: function(e,ui) {
45
+ // ui.helper.css("outline", "1px dotted green");
46
+ // },
47
+ // out: function(e,ui) {
48
+ // ui.helper.css("outline", "1px dotted red");
49
+ // }
50
+ });
51
+ }
52
+ else{
53
+ this.listEl.sortable(options);
54
+ }
55
+ },
56
+
57
+
58
+
59
+ handleSort: function(e, ui){
60
+ if (ui.sender) return; // handled by handleReceive
61
+ var item = ui.item;
62
+ var pos = this.findLiPosition(item);
63
+ if (pos == -1) return;
64
+ if (this.listEl.data('sortable').fromOutside){ // hack to work around strange jquery ui behaviour...
65
+ return this.acceptFromSender(null, ui.item, pos);
66
+ }
67
+ this.remote('sort', this.getSortItemId(item), pos);
68
+ },
69
+
70
+
71
+ handleReceive: function(e, ui){
72
+ if (ui.sender == ui.item){
73
+ // the item is received from a draggable, not from a list. For some reason the receiver is not
74
+ // yet ready to find the position; will call acceptFromSender from handleSort.
75
+ return;
76
+ }
77
+ var pos = this.findLiPosition(ui.item);
78
+ return this.acceptFromSender(ui.sender, ui.item, pos);
79
+ },
80
+
81
+ handleTreeUpdate: function(e, ui){
82
+ var parentId = ui.item.parents('li.tree').eq(0).dataObjectKey();
83
+ var prevId = ui.item.prev('li.tree').dataObjectKey();
84
+ this.remote('tree_sort', this.getItemId(ui.item), parentId, prevId);
85
+ },
86
+
87
+ handleTreeDrop: function(e, ui){
88
+ if (e.target.parentNode == ui.draggable[0]) return false; //dropped over itself
89
+ console.log('dropped inside');
90
+ var dropLi = $(e.target.parentNode);
91
+ var subUl = $("> "+this.listTagName, dropLi);
92
+ if (subUl.length == 0){
93
+ subUl = $("<"+this.listTagName+" />").appendTo(dropLi);
94
+ this.el.treeview({add: e.target.parentNode});
95
+ }
96
+ subUl.append(ui.draggable);
97
+ var parentId = $(e.target).parents('li.tree').eq(0).dataObjectKey();
98
+ var prevId = null;
99
+ this.remote('tree_sort', ui.draggable.dataObjectKey(), parentId, prevId);
100
+ return false;
101
+ },
102
+
103
+ acceptFromSender: function(sender, item, pos){
104
+ console.error("Accept from sender must be implemented by the widget instance");
105
+ },
106
+
107
+ findLiPosition: function(item){
108
+ var cnt = 1;
109
+ var li = $('> li', this.listEl);
110
+ li.each(function(){
111
+ if (this == item.get(0)) return false;
112
+ cnt++;
113
+ });
114
+ if (cnt > li.length) return -1; // the row was dropped outside
115
+ return cnt;
116
+ },
117
+
118
+ getSortItemId: function(li){
119
+ var k = $('> .sort-key', li);
120
+ if (k.length > 0) return k.text();
121
+ return li.dataObjectKey();
122
+ }
123
+
124
+ });
@@ -7,7 +7,7 @@ function $W(path){
7
7
  if (Spider.widgets[path]) return Spider.widgets[path];
8
8
  var wdgt_id = path.replace(/\//g, '-');
9
9
  var wdgt = $('#'+wdgt_id);
10
- if (!wdgt) return null;
10
+ if (wdgt.length == 0) return null;
11
11
  return Spider.Widget.initFromEl(wdgt);
12
12
  }
13
13
 
@@ -23,6 +23,8 @@ Spider.Widget = Class.extend({
23
23
  init: function(container, path, config){
24
24
  this.el = container;
25
25
  this.path = path;
26
+ var pathParts = path.split('/');
27
+ this.widgetId = pathParts[pathParts.length - 1];
26
28
  this.backend = new Spider.WidgetBackend(this);
27
29
  this.readyFunctions = [];
28
30
  config = $.extend({}, config);
@@ -30,15 +32,26 @@ Spider.Widget = Class.extend({
30
32
  this.model = config.model;
31
33
  Spider.widgets[path] = this;
32
34
  this.events = [];
35
+ this.onWidgetCallbacks = {};
36
+ this.widgets = {};
37
+ this.findWidgets();
33
38
  this.startup();
34
39
  this.ready();
35
40
  this.applyReady();
41
+ this.plugins = [];
42
+ if (this.includePlugins) for (var i=0; i<this.includePlugins.length; i++){
43
+ this.plugin(this.includePlugins[i]);
44
+ }
36
45
  },
37
46
 
38
47
  remote: function(){
39
48
  var args = Array.prototype.slice.call(arguments);
40
49
  var method = args.shift();
41
- return this.backend.send(method, args);
50
+ var options = {};
51
+ if ($.isFunction(args[args.length-1])){
52
+ options.callback = args.pop();
53
+ }
54
+ return this.backend.send(method, args, options);
42
55
  },
43
56
 
44
57
  onReady: function(callback){
@@ -53,9 +66,17 @@ Spider.Widget = Class.extend({
53
66
  },
54
67
 
55
68
  reload: function(params, callback){
69
+ if (!callback && $.isFunction(params)){
70
+ callback = params;
71
+ params = {};
72
+ }
56
73
  $C.loadWidget(this.path, params, callback);
57
74
  },
58
75
 
76
+ isLoaded: function(){
77
+ return !this.el.is(':empty');
78
+ },
79
+
59
80
  startup: function(){},
60
81
  ready: function(){},
61
82
  update: function(){},
@@ -63,6 +84,7 @@ Spider.Widget = Class.extend({
63
84
  replaceHTML: function(html){
64
85
  var el = $(html);
65
86
  this.el.html(el.html());
87
+ this.findWidgets();
66
88
  this.update();
67
89
  this.ready();
68
90
  Spider.newHTML(this.el);
@@ -72,11 +94,34 @@ Spider.Widget = Class.extend({
72
94
 
73
95
  replaceEl: function(el){
74
96
  this.el = el;
97
+ this.findWidgets();
75
98
  this.update();
76
99
  this.ready();
77
100
  this.applyReady();
78
101
  },
79
102
 
103
+ findWidgets: function(){
104
+ var self = this;
105
+ $('.widget', this.el).filter(function(index){
106
+ if ($(this).parents('.widget').get(0) == self.el.get(0)) return true;
107
+ return false;
108
+ }).each(function(){
109
+ var $this = $(this);
110
+ var w = $this.spiderWidget();
111
+ if (!self.widgets[w.widgetId]) self.addWidget(w.widgetId, w);
112
+ else self.widgets[w.widgetId].replaceEl($this);
113
+ });
114
+ },
115
+
116
+ addWidget: function(id, w){
117
+ this.widgets[id] = w;
118
+ if (this.onWidgetCallbacks[id]){
119
+ for (var i=0; i<this.onWidgetCallbacks[id].length; i++){
120
+ this.onWidgetCallbacks[id][i].call(this, w);
121
+ }
122
+ }
123
+ },
124
+
80
125
  paramName: function(key){
81
126
  var pathParts = this.path.split('/');
82
127
  var param = "_w";
@@ -91,69 +136,111 @@ Spider.Widget = Class.extend({
91
136
  },
92
137
 
93
138
 
139
+ ajaxifyAll: function(options){
140
+ var els = $('form:not(.ajaxified), a:not(.ajaxified)', this.el);
141
+ if (!options) options = {};
142
+ if (options.filter) els = els.filter(options.filter);
143
+ if (options.not) els = els.not(options.not);
144
+ this.ajaxify(els, options);
145
+ },
146
+
147
+ findWidgetsAjaxifiable: function(options){
148
+ var selfEl = this.el.get(0);
149
+ return $('form:not(.ajaxified), a:not(.ajaxified)', this.el).filter(function(index){
150
+ var p = $(this).parent();
151
+ while (p){
152
+ if (p.is('.widget')){
153
+ if (p.get(0) == selfEl) return true;
154
+ return false;
155
+ }
156
+ p = p.parent();
157
+ }
158
+ return false;
159
+ });
160
+ },
161
+
162
+
94
163
  ajaxify: function(el, options){
95
164
  var w = this;
165
+ if (!el || !el.eq){
166
+ options = el;
167
+ el = this.findWidgetsAjaxifiable();
168
+ }
96
169
  if (!options) options = {};
97
170
  el.each(function(){
98
171
  var $this = $(this);
99
172
  if (this.tagName == 'FORM'){
100
- w.ajaxifyForm($(this));
173
+ w.ajaxifyForm($(this), options);
101
174
  }
102
175
  else if (this.tagName == 'A'){
103
- $this.click(function(e){
104
- if (options.before){
105
- var res = options.before.apply(this);
106
- if (res === false) return false ;
107
- }
108
- e.preventDefault();
109
- var a = $(e.target);
110
- var url = $(this).attr('href');
111
- var parts = url.split('?');
112
- url = parts[0]; //+'.json';
113
- url += '?';
114
- if (parts[1]) url += parts[1]+'&';
115
- url += '_wt='+w.path;
116
- w.setLoading();
117
- $.ajax({
118
- url: url,
119
- type: 'GET',
120
- dataType: 'html',
121
- success: function(res){
122
- w.replaceHTML(res);
123
- w.removeLoading();
124
- }
125
- });
126
- });
176
+ w.ajaxifyLink($(this), options);
127
177
  }
128
178
  });
129
179
 
130
180
  },
131
181
 
132
- ajaxifyForm: function(form){
133
- var w = this;
182
+ ajaxifyForm: function(form, options){
134
183
  var isForm = form.get(0).tagName == 'FORM';
135
- $('input[type=submit]', form).click(function(e){
184
+ if (!options) options = {};
185
+ $('input[type=submit]', form).addClass('ajaxified').bind('click.ajaxify', function(e){
186
+ var $this = $(this);
187
+ var w = $this.parentWidget();
136
188
  e.preventDefault();
137
189
  w.setLoading();
138
- var submitName = $(this).attr('name');
139
- var submitValue = $(this).val();
190
+ var submitName = $this.attr('name');
191
+ var submitValue = $this.val();
140
192
  form.ajaxSubmit({
141
193
  dataType: 'html',
142
194
  semantic: !isForm,
143
195
  beforeSubmit: function(data, form, options){
144
196
  data.push({name: submitName, value: submitValue});
145
197
  data.push({name: '_wt', value: w.path});
198
+ if (options.before) options.before();
146
199
  },
147
200
  success: function(res){
148
201
  w.replaceHTML(res);
149
202
  w.removeLoading();
203
+ if (options.onLoad) options.onLoad(form);
204
+ w.trigger('ajaxifyLoad', form);
205
+ }
206
+ });
207
+ });
208
+ },
209
+
210
+ ajaxifyLink: function(a, options){
211
+ var w = this;
212
+ if (!options) options = {};
213
+ a.addClass('ajaxified').bind('click.ajaxify', function(e){
214
+ if (options.before){
215
+ var res = options.before.apply(w);
216
+ if (res === false) return false ;
217
+ }
218
+ e.preventDefault();
219
+ var a = $(e.target);
220
+ var url = $(this).attr('href');
221
+ var parts = url.split('?');
222
+ url = parts[0]; //+'.json';
223
+ url += '?';
224
+ if (parts[1]) url += parts[1]+'&';
225
+ url += '_wt='+w.path;
226
+ if (options.before) options.before();
227
+ w.setLoading();
228
+ $.ajax({
229
+ url: url,
230
+ type: 'GET',
231
+ dataType: 'html',
232
+ success: function(res){
233
+ w.replaceHTML(res);
234
+ w.removeLoading();
235
+ if (options.onLoad) options.onLoad(a);
236
+ w.trigger('ajaxifyLoad', a);
150
237
  }
151
238
  });
152
239
  });
153
240
  },
154
241
 
155
242
  setLoading: function(){
156
- if (this.el.is(':empty')){
243
+ if (this.el.is(':empty') || this.el.children().hasClass('empty-placeholder')){
157
244
  this.el.addClass('loading-empty');
158
245
  }
159
246
  else{
@@ -201,18 +288,73 @@ Spider.Widget = Class.extend({
201
288
  },
202
289
 
203
290
  bind: function(eventName, callback){
204
- if (!this.events[eventName]){
205
- this.events[eventName] = [];
291
+ var handleObj = {
292
+ callback: callback
293
+ };
294
+ if ( eventName.indexOf(".") > -1 ) {
295
+ var namespaces = eventName.split(".");
296
+ eventName = namespaces.shift();
297
+ handleObj.namespace = namespaces.slice(0).sort().join(".");
206
298
  }
207
- this.events[eventName].push(callback);
299
+ if (!this.events[eventName]) this.events[eventName] = [];
300
+ this.events[eventName].push(handleObj);
208
301
  },
209
302
 
303
+ on: function(eventName, callback){ return this.bind(eventName, callback); },
304
+
210
305
  trigger: function(eventName){
306
+ if ( eventName.indexOf(".") > -1 ) {
307
+ var namespaces = eventName.split(".");
308
+ eventName = namespaces.shift();
309
+ namespace = namespaces.slice(0).sort().join(".");
310
+ }
211
311
  if (!this.events[eventName]) return;
212
312
  var args = Array.prototype.slice.call(arguments, 1);
213
313
  for (var i=0; i < this.events[eventName].length; i++){
214
- this.events[eventName][i].apply(this, args);
314
+ this.events[eventName][i].callback.apply(this, args);
315
+ }
316
+ },
317
+
318
+ unbind: function(eventName){
319
+ var namespace = null;
320
+ if ( eventName.indexOf(".") > -1 ) {
321
+ var namespaces = eventName.split(".");
322
+ eventName = namespaces.shift();
323
+ namespace = namespaces.slice(0).sort().join(".");
215
324
  }
325
+ if (namespace){
326
+ for (var i=0; i<this.events[eventName].length; i++){
327
+ if (this.events[eventName][i].namespace == namespace){
328
+ this.events[eventName].splice(i);
329
+ }
330
+ }
331
+ }
332
+ else this.events[eventName] = [];
333
+ },
334
+
335
+ plugin: function(pClass, prop){
336
+ if (prop) pClass = pClass.extend(prop);
337
+ this.plugins[pClass] = new pClass(this);
338
+ var plugin = this.plugins[pClass];
339
+ for (var name in pClass.prototype){
340
+ if (name.substring(0, 1) == '_') continue;
341
+ if (typeof pClass.prototype[name] == "function" && !this[name]){
342
+ this[name] = function(name){
343
+ return function(){
344
+ return plugin[name].apply(this, arguments);
345
+ };
346
+ }(name);
347
+ }
348
+ }
349
+ },
350
+
351
+ widget: function(id){
352
+ return this.widgets[id];
353
+ },
354
+
355
+ onWidget: function(id, callback){
356
+ if (!this.onWidgetCallbacks[id]) this.onWidgetCallbacks[id] = [];
357
+ this.onWidgetCallbacks[id].push(callback);
216
358
  }
217
359
 
218
360
 
@@ -276,20 +418,29 @@ Spider.WidgetBackend = Class.extend({
276
418
  },
277
419
 
278
420
  send: function(method, args, options){
279
- var url = this.urlForMethod(method);
280
- for (var i=0; i<args.length; i++){
281
- url += '&_wp[]='+args[i];
282
- }
283
- var data = {};
284
- var callback = this.widget[method+'_response'];
285
- if (!callback) callback = function(){};
421
+ if (!options) options = {};
286
422
  var defaults = {
287
- url: url,
288
423
  type: 'POST',
289
- success: callback,
290
- data: data
424
+ dataType: 'json'
291
425
  };
292
426
  options = $.extend(defaults, options);
427
+ if (!options.format) options.format = options.dataType;
428
+ var url = this.baseUrl;
429
+ var data = {};
430
+ if ($.isPlainObject(args[0])) data = args[0];
431
+ else data = {'_wp': args};
432
+ $.extend(data, {
433
+ '_wt': this.widget.path,
434
+ '_we': method,
435
+ '_wf': options.format
436
+ });
437
+ var callback = this.widget[method+'_response'];
438
+ if (!callback) callback = options.callback;
439
+ if (!callback) callback = function(){};
440
+ delete(options.callback);
441
+ options.success = callback;
442
+ options.data = data;
443
+ options.url = url;
293
444
  $.ajax(options);
294
445
  }
295
446
 
@@ -327,6 +478,7 @@ Spider.defineWidget = function(name, parent, w){
327
478
  });
328
479
  });
329
480
  }
481
+ return widget;
330
482
  };
331
483
 
332
484
  Spider.Controller = Class.extend({
@@ -335,8 +487,14 @@ Spider.Controller = Class.extend({
335
487
  var url = ''+document.location;
336
488
  var slashPos = url.lastIndexOf('/');
337
489
  url = url.substr(0, slashPos);
338
- this.url = url;
490
+ this.setUrl(url);
339
491
  },
492
+
493
+ setUrl: function(url){
494
+ this.url = url;
495
+ this.publicUrl = this.url+'/public'; // FIXME
496
+ this.homeUrl = this.url+'/_h';
497
+ },
340
498
 
341
499
  remote: function(method, params, callback, options){
342
500
  var args = Array.prototype.slice.call(arguments);
@@ -345,8 +503,9 @@ Spider.Controller = Class.extend({
345
503
  var defaults = {
346
504
  url: url,
347
505
  type: 'POST',
348
- complete: callback,
349
- data: params
506
+ success: callback,
507
+ data: params,
508
+ dataType: 'json'
350
509
  };
351
510
  options = $.extend(defaults, options);
352
511
  $.ajax(options);
@@ -431,7 +590,9 @@ $(document).ready(function(){
431
590
 
432
591
  $.fn.spiderWidget = function(){
433
592
  if (!this.attr('id')) return;
434
- return $W(Spider.Widget.pathFromId(this.attr('id')));
593
+ var path = Spider.Widget.pathFromId(this.attr('id'));
594
+ if (Spider.widgets[path]) return Spider.widgets[path];
595
+ return Spider.Widget.initFromEl(this);
435
596
  };
436
597
 
437
598
  $.fn.parentWidget = function(){