modeljs 0.0.1

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.
@@ -0,0 +1,121 @@
1
+
2
+ Model.Attribute.Select = Model.Attribute.extend({
3
+
4
+ text: '',
5
+ multiple: false,
6
+
7
+ needs_options: function()
8
+ {
9
+ return true;
10
+ },
11
+
12
+ view: function()
13
+ {
14
+ var html = this.text && this.text.length > 0 ? this.text : this.empty_text;
15
+ //return $('<a/>')
16
+ // .attr('title', 'Click to edit')
17
+ // .attr('href', 'javascript:{};')
18
+ // .addClass('model_attribute_select')
19
+ // .html(html);
20
+ return $('<div/>')
21
+ .html(html);
22
+ },
23
+
24
+ form: function()
25
+ {
26
+ var form = $('<form/>')
27
+ var select = $('<select></select>')
28
+ .attr('id', this.base)
29
+ .attr('name', this.base);
30
+
31
+ if (this.multiple)
32
+ {
33
+ select.attr('multiple', 'multiple');
34
+ select.attr('size', '9');
35
+ }
36
+
37
+ var this2 = this;
38
+ var options = Model.get_attribute_options(this.model.name, this.name);
39
+ $.each(options, function(i, option)
40
+ {
41
+ if (option.children != null) // Option group
42
+ {
43
+ var optgroup = $('<optgroup></optgroup>');
44
+ optgroup.attr('label', option.title);
45
+
46
+ $.each(option.children, function(j, option2)
47
+ {
48
+ var opt = $('<option></option>')
49
+ .attr('text', option2.text)
50
+ .val(option2.value);
51
+
52
+ if (!this2.multiple)
53
+ opt.attr('selected', (option2.value == this2.value));
54
+ else
55
+ {
56
+ opt.attr('selected', false);
57
+ for (var k=0; k<this2.value.length; k++)
58
+ if (option2.value == this2.value[k])
59
+ {
60
+ opt.attr('selected', true);
61
+ break;
62
+ }
63
+ }
64
+ optgroup.append(opt);
65
+ });
66
+
67
+ select.append(optgroup);
68
+ }
69
+ else
70
+ {
71
+ var opt = $('<option></option>')
72
+ .val(option.value)
73
+ .text(option.text);
74
+
75
+ if (!this2.multiple)
76
+ {
77
+ opt.attr('selected', (option.value == this2.value));
78
+ }
79
+ else
80
+ {
81
+ opt.attr('selected', false);
82
+ for (var j=0; j<this2.value.length; j++)
83
+ if (option.value == this2.value[j])
84
+ {
85
+ opt.attr('selected', true);
86
+ break;
87
+ }
88
+ }
89
+ select.append(opt);
90
+ }
91
+ });
92
+ form.append(select);
93
+ if (this.multiple)
94
+ {
95
+ form.append("<br/>");
96
+ form.append(
97
+ $('<input/>')
98
+ .attr('type', 'button')
99
+ .val('Clear')
100
+ .addClass('update_btn')
101
+ .click(function() { $('#' + this.base).selectedIndex = -1; })
102
+ );
103
+ }
104
+
105
+ return form;
106
+ },
107
+
108
+ form_value: function()
109
+ {
110
+ if (!this.multiple)
111
+ return $('#' + this.base + ' option:selected').val();
112
+
113
+ var select = $('#' + this.base);
114
+ val = [];
115
+ $('#' + this.base + ' option:selected').each(function(i, opt) {
116
+ val[val.length] = $(opt).val();
117
+ });
118
+ return val;
119
+ }
120
+
121
+ });
@@ -0,0 +1,42 @@
1
+
2
+ Model.Attribute.Textarea = Model.Attribute.extend({
3
+
4
+ show_controls: true,
5
+ normal_text: false,
6
+ width: 200,
7
+ height: 100,
8
+
9
+ view: function()
10
+ {
11
+ html = this.empty_text;
12
+ if (this.text && this.text.length > 0) html = this.text;
13
+ else if (this.value && this.value.length > 0) html = this.value;
14
+ //return $('<a/>')
15
+ // .attr('title', 'Click to edit')
16
+ // .attr('href', 'javascript:{};')
17
+ // .css({
18
+ // display: 'block',
19
+ // width: this.width
20
+ // })
21
+ // .html(html);
22
+ return $('<div/>')
23
+ .css({
24
+ display: 'block',
25
+ width: this.width
26
+ })
27
+ .html(html);
28
+ },
29
+
30
+ form: function()
31
+ {
32
+ return $('<form/>')
33
+ .append($('<textarea/>')
34
+ .attr('id', this.base)
35
+ .attr('name', this.base)
36
+ .val(this.value)
37
+ .css('width', this.width)
38
+ .css('height', this.height)
39
+ );
40
+ }
41
+
42
+ });
@@ -0,0 +1,63 @@
1
+
2
+ Model.Attribute.Text = Model.Attribute.extend({
3
+
4
+ show_reminder: true,
5
+
6
+ view: function()
7
+ {
8
+ var html = false;
9
+ if (typeof(this.value) == 'string')
10
+ html = this.value && this.value.length > 0 ? this.value : this.empty_text;
11
+ else if (typeof(this.value) == 'number')
12
+ html = this.value != null ? this.value : this.empty_text;
13
+ else
14
+ html = this.value;
15
+
16
+ //return $('<a/>')
17
+ // .attr('title', 'Click to edit')
18
+ // .attr('href', 'javascript:{};')
19
+ // .html(html);
20
+ return $('<div/>')
21
+ .html(html);
22
+ },
23
+
24
+ form: function()
25
+ {
26
+ var this2 = this;
27
+ var input = $('<input/>')
28
+ .attr('type', 'text')
29
+ .attr('id', this.base)
30
+ .attr('name', this.base)
31
+ .val(this.value);
32
+ if (this.show_reminder)
33
+ input.keyup(function (){ this2.flash_reminder(); })
34
+ if (this.css)
35
+ input.css(this.css);
36
+ var form = $('<form/>').append(input);
37
+ if (this.show_reminder)
38
+ form.append($('<span/>').attr('id', this.base + '_reminder').css('font-size', '75%'));
39
+
40
+ return form;
41
+ },
42
+
43
+ flash_reminder: function()
44
+ {
45
+ $('#' + this.base + '_reminder')
46
+ .html(' (Enter to update, Escape to cancel)')
47
+ .delay(1000)
48
+ .fadeOut({ done: function() { $(this).empty().show(); }});
49
+ },
50
+
51
+ post_form_display: function()
52
+ {
53
+ // Put the focus at the end of the string
54
+ $('#' + this.base).focus();
55
+ val = $('#' + this.base).val();
56
+ $('#' + this.base).val('');
57
+ $('#' + this.base).val(val);
58
+
59
+ if (this.show_reminder)
60
+ this.flash_reminder();
61
+ }
62
+
63
+ });
@@ -0,0 +1,31 @@
1
+
2
+ Model.Attribute.Time = Model.Attribute.extend({
3
+
4
+ view: function()
5
+ {
6
+ var html = this.value && this.value.length > 0 ? this.value : this.empty_text;
7
+ return $('<a/>')
8
+ .attr('title', 'Click to edit')
9
+ .attr('href', 'javascript:{};')
10
+ .addClass('model_attribute_text')
11
+ .html(html);
12
+ },
13
+
14
+ form: function()
15
+ {
16
+ return $('<form/>')
17
+ .append($('<input/>')
18
+ .attr('type', 'text')
19
+ .attr('id', this.base)
20
+ .attr('name', this.base)
21
+ .val(this.value)
22
+ .addClass('text_box')
23
+ );
24
+ },
25
+
26
+ post_form_display: function()
27
+ {
28
+ $('#' + this.base).timepicker({ ampm: true });
29
+ }
30
+
31
+ });
@@ -0,0 +1,95 @@
1
+
2
+ Model.Attribute.Video = Model.Attribute.extend({
3
+
4
+ show_reminder: true,
5
+ thumb_width: 320,
6
+ thumb_height: 240,
7
+
8
+ view: function()
9
+ {
10
+ var html = false;
11
+ if (typeof(this.value) == 'string')
12
+ html = this.value && this.value.length > 0 ? this.value : this.empty_text;
13
+ else if (typeof(this.value) == 'number')
14
+ html = this.value != null ? this.value : this.empty_text;
15
+ else
16
+ html = this.value;
17
+ var div = $('<div/>').html(html);
18
+ var iframe = this.video_iframe(this.value);
19
+ if (iframe)
20
+ div.append($('<br/>')).append(iframe);
21
+ return div;
22
+ },
23
+
24
+ form: function()
25
+ {
26
+ var this2 = this;
27
+ var input = $('<input/>')
28
+ .attr('type', 'text')
29
+ .attr('id', this.base)
30
+ .attr('name', this.base)
31
+ .val(this.value);
32
+ if (this.show_reminder)
33
+ input.keyup(function (){ this2.flash_reminder(); })
34
+ if (this.css)
35
+ input.css(this.css);
36
+ var form = $('<form/>').append(input);
37
+ if (this.show_reminder)
38
+ form.append($('<span/>').attr('id', this.base + '_reminder').css('font-size', '75%'));
39
+
40
+ var iframe = this.video_iframe(this.value);
41
+ if (iframe)
42
+ form.append($('<br/>')).append(iframe);
43
+
44
+ return form;
45
+ },
46
+
47
+ flash_reminder: function()
48
+ {
49
+ $('#' + this.base + '_reminder')
50
+ .html(' (Enter to update, Escape to cancel)')
51
+ .delay(1000)
52
+ .fadeOut({ done: function() { $(this).empty().show(); }});
53
+ },
54
+
55
+ post_form_display: function()
56
+ {
57
+ // Put the focus at the end of the string
58
+ $('#' + this.base).focus();
59
+ val = $('#' + this.base).val();
60
+ $('#' + this.base).val('');
61
+ $('#' + this.base).val(val);
62
+
63
+ if (this.show_reminder)
64
+ this.flash_reminder();
65
+ },
66
+
67
+ video_iframe: function()
68
+ {
69
+ if (this.value == null || this.value.length == 0)
70
+ return false;
71
+
72
+ if (this.value.match(/youtube\.com/))
73
+ {
74
+ var v = this.value.replace("youtube.com/watch?v=", '');
75
+ return $('<iframe/>')
76
+ .attr('type', 'text/html')
77
+ .attr('width', this.thumb_width)
78
+ .attr('height', this.thumb_height)
79
+ .attr('frameborder', '0')
80
+ .attr('src', "http://www.youtube.com/embed/" + v + "?autoplay=0");
81
+ }
82
+ if (this.value.match(/vimeo\.com/))
83
+ {
84
+ var v = this.value.replace("vimeo.com/", '');
85
+ return $('<iframe/>')
86
+ .attr('type', 'text/html')
87
+ .attr('width', this.thumb_width)
88
+ .attr('height', this.thumb_height)
89
+ .attr('frameborder', '0')
90
+ .attr('src', "http://player.vimeo.com/video/" + v);
91
+ }
92
+ return false;
93
+ }
94
+
95
+ });
@@ -0,0 +1,69 @@
1
+
2
+ Model.Attribute = Class.extend({
3
+ name: '',
4
+ nice_name: '',
5
+ value: '',
6
+ model: false,
7
+ base: '', // model.name + '_' + model.id + '_' + attribute.name
8
+ type: false,
9
+ container: false,
10
+ controls: false,
11
+ show_controls: false,
12
+ message: false,
13
+ empty_text: '',
14
+ update_url: false, // If this attribute has a special update URL
15
+ custom_form: false, // If the attribute needs a custom form (used in file and image)
16
+ css: false,
17
+
18
+ init: function(params)
19
+ {
20
+ for (var thing in params)
21
+ this[thing] = params[thing];
22
+ this.set_base(this.base);
23
+ if (!this.nice_name) this.nice_name = this.name.replace('_', ' ').capitalize();
24
+ },
25
+
26
+ set_base: function(base)
27
+ {
28
+ this.base = base;
29
+ if (!this.container) this.container = this.base + '_container';
30
+ if (!this.controls) this.controls = this.base + '_controls';
31
+ if (!this.message) this.message = this.base + '_message';
32
+ },
33
+
34
+ // Whether or not the attribute needs to go get options via ajax
35
+ needs_options: function() { return false; },
36
+
37
+ // Returns the viewable representation of the attribute.
38
+ view: function() { return false; },
39
+
40
+ // Returns a form without update or cancel buttons (Assumes options already populated)
41
+ form: function() { return false; },
42
+
43
+ // Returns the current value in the visible form.
44
+ form_value: function() { return $('#' + this.base).val(); },
45
+
46
+ // Returns the values form the form that will be sent to ajax_update
47
+ form_values: function() {
48
+ var vals = {};
49
+ vals[this.name] = this.form_value();
50
+ return vals;
51
+ },
52
+
53
+ // Runs after the view or form is displayed on the screen.
54
+ post_view_display: function() {},
55
+ post_form_display: function() { $('#' + this.base).focus(); },
56
+
57
+ // Notes
58
+ loading: function(str) { this.show_message('loading_small', str); },
59
+ note: function(str) { this.show_message('note_small' , str); },
60
+ success: function(str) { this.show_message('note_small success' , str); },
61
+ error: function(str) { this.show_message('note_small error' , str); },
62
+ show_message: function(class_names, str) {
63
+ $('#' + this.message).empty().removeClass('loading_small');
64
+ $('#' + this.message).addClass(class_names).html(str);
65
+ },
66
+
67
+ // If the attribute needs to handle any finished uploads
68
+ upload_finished: function(e, data) {}
69
+ });
@@ -0,0 +1,13 @@
1
+ /*
2
+ Class: Model.Form
3
+ Author: William Barry (william@nine.is)
4
+ Description: Abstract form class to encapsulate add and edit forms for models.
5
+ */
6
+
7
+ Model.Form.Embedded = Class.extend({
8
+
9
+ add: function() { return false; },
10
+ edit: function() { return false; },
11
+ listing: function() { return false; },
12
+
13
+ });
@@ -0,0 +1,257 @@
1
+ /*
2
+ Class: Model.Form
3
+ Author: William Barry (william@nine.is)
4
+ Description: Abstract form class to encapsulate add and edit forms for models.
5
+ */
6
+
7
+ Model.Form = Class.extend({
8
+
9
+ class_name: 'Model.Form',
10
+ model: false,
11
+ message: false,
12
+ adding_message: null,
13
+ deleting_message: null,
14
+ delete_message: null,
15
+
16
+ // Listing info
17
+ listing_models: false,
18
+
19
+ init: function(model, params)
20
+ {
21
+ this.model = model;
22
+ if (params)
23
+ for (var thing in params)
24
+ this[thing] = params[thing];
25
+
26
+ if (!this.message ) this.message = model.name + '_message';
27
+ if (!this.adding_message ) this.adding_message = 'Adding ' + this.model.name + '...';
28
+ if (!this.deleting_message) this.deleting_message = 'Deleting ' + this.model.name + '...';
29
+ if (!this.delete_message ) this.delete_message = "Are you sure you want to delete the " + this.model.name + "? This can't be undone.";
30
+ },
31
+
32
+ // Returns the form for adding the model or false for an embedded form.
33
+ add: function()
34
+ {
35
+ var m = this.model;
36
+ var tbody = $('<tbody/>');
37
+ $(m.attributes).each(function(i, a) {
38
+ if (a.type == 'hidden')
39
+ return;
40
+ tbody.append($('<tr/>')
41
+ .append($('<td/>').css('vertical-align', 'top').html(a.nice_name + ':'))
42
+ .append($('<td/>').css('vertical-align', 'top').attr('id', m.name + '_' + m.id + '_' + a.name + '_container')
43
+ .append(a.form().children())
44
+ )
45
+ );
46
+ });
47
+ var form = $('<form/>')
48
+ .attr('id', 'new_' + m.name + '_form')
49
+ .submit(function() { return false; })
50
+ .append($('<table/>').append(tbody))
51
+ .append($('<div/>').attr('id', this.message_div))
52
+ .append($('<p/>')
53
+ .append($('<input/>').attr('type', 'submit').val('Add ' + m.name).click(function() { m.ajax_add(); }))
54
+ .append($('<input/>').attr('type', 'button').val('Back').click(function() { window.location = m.listing_url; }))
55
+ );
56
+ return form;
57
+ },
58
+
59
+ // Runs after the add form has been displayed
60
+ post_add_display: function() {},
61
+
62
+ // Returns the form for editing a model or false for an embedded form.
63
+ edit: function()
64
+ {
65
+ var m = this.model;
66
+
67
+ var this2 = this;
68
+ var tbody = $('<tbody/>');
69
+ $(m.attributes).each(function(i, a) {
70
+ if (a.type == 'hidden')
71
+ return;
72
+ tbody.append($('<tr/>')
73
+ .append($('<td/>').css('vertical-align', 'top').html(a.nice_name + ':'))
74
+ .append($('<td/>').css('vertical-align', 'top').attr('id', m.name + '_' + m.id + '_' + a.name + '_container'))
75
+ );
76
+ });
77
+
78
+ var div = $('<div/>')
79
+ .append($('<table/>').append(tbody))
80
+ .append($('<div/>').attr('id', this.message))
81
+ .append($('<p/>')
82
+ .append($('<input/>').attr('type', 'button').val('Back').click(function() { window.location = m.listing_url; }))
83
+ .append(' ')
84
+ .append($('<input/>').attr('type', 'button').val('Delete ' + m.name).click(function() { m.ajax_delete(); }))
85
+ );
86
+ return div;
87
+ },
88
+
89
+ // Runs after the edit form has been displayed
90
+ post_edit_display: function() {},
91
+
92
+ listing_view: function()
93
+ {
94
+ var tr = $('<tr/>');
95
+ var m = this.model;
96
+ $(this.model.attributes).each(function(i, a) {
97
+ if (a.type == 'hidden') return;
98
+ tr.append(
99
+ $('<td/>')
100
+ .attr('id', m.name + '_' + m.id + '_' + a.name + '_container')
101
+ .append(a.view())
102
+ );
103
+ });
104
+ return tr;
105
+ },
106
+
107
+ post_listing_view_display: function() {},
108
+
109
+ // Returns a listing table of models or false for an embedded listing
110
+ listing_form: function()
111
+ {
112
+ var m = this.model;
113
+ var count = m.unhidden_attribute_count();
114
+ var tr = $('<tr/>').attr('id', m.name + '_' + m.id + '_listing_container');
115
+ $(this.model.attributes).each(function(i, a) {
116
+ if (a.type == 'hidden')
117
+ return;
118
+ var c = '';
119
+ if (i == 0) c = 'edit_listing_first_cell';
120
+ else if (i == count-1) c = 'edit_listing_last_cell';
121
+ else c = 'edit_listing_cell';
122
+
123
+ tr.append($('<td/>')
124
+ .css('vertical-align', 'top')
125
+ .addClass(c)
126
+ .attr('id', m.name + '_' + m.id + '_' + a.name + '_container')
127
+ );
128
+ });
129
+ return tr;
130
+ },
131
+
132
+ // Runs after the listing has been displayed
133
+ post_listing_form_display: function() {},
134
+
135
+ listing_view: function()
136
+ {
137
+ var m = this.model;
138
+ var tr = $('<tr/>').attr('id', m.name + '_' + m.id + '_listing_container');
139
+ $(this.model.attributes).each(function(i, a) {
140
+ if (a.type == 'hidden')
141
+ return;
142
+ tr.append($('<td/>').css('vertical-align', 'top').attr('id', m.name + '_' + m.id + '_' + a.name + '_container'));
143
+ });
144
+ return tr;
145
+ },
146
+
147
+ post_listing_view_display: function() {},
148
+
149
+ // Gets the latest rows for the listing
150
+ refresh_listing_models: function(done)
151
+ {
152
+ var this2 = this;
153
+ var obj = Model.parse_url(this.model.listing_url);
154
+ $.ajax({
155
+ url: obj.url,
156
+ type: obj.verb,
157
+ success: function(models) {
158
+ this2.refresh_listing_models_helper(models);
159
+ done();
160
+ },
161
+ error: function() {
162
+ this2.ajax_error('Error');
163
+ }
164
+ });
165
+ },
166
+
167
+ refresh_listing_models_helper: function(models)
168
+ {
169
+ var this2 = this;
170
+ this.listing_models = [];
171
+ $(models).each(function(i, m) {
172
+
173
+ var attribs = [];
174
+ $(m.attributes).each(function(i, a) {
175
+
176
+ // Find the original attribute with the same name
177
+ var attrib = false;
178
+ $(this2.model.attributes_original).each(function(j, a2) {
179
+ if (a2.name == a.name)
180
+ {
181
+ attrib = $.extend(true, {}, a2);
182
+ return false;
183
+ }
184
+ });
185
+
186
+ // Copy new data into the original attribute hash
187
+ for (var thing in a)
188
+ attrib[thing] = a[thing];
189
+ attrib.base = this2.model.name + '_' + m.id + '_' + attrib.name;
190
+
191
+ attribs.push(attrib);
192
+ });
193
+ var params = {
194
+ name: this2.model.name,
195
+ id: m.id,
196
+ form: this2.class_name,
197
+ attributes: attribs,
198
+ create_url: this2.model.create_url,
199
+ update_url: this2.model.update_url,
200
+ delete_url: this2.model.delete_url,
201
+ listing_url: this2.model.listing_url,
202
+ listing_edit: this2.model.listing_edit,
203
+ listing_delete: this2.model.listing_delete,
204
+ listing_empty_text: this2.model.listing_empty_text,
205
+ is_listing: true
206
+ };
207
+ var m2 = new Model(params);
208
+ this2.listing_models.push(m2);
209
+ });
210
+ },
211
+
212
+ // Returns the values of the add form that will be sent to model.ajax_add
213
+ add_values: function()
214
+ {
215
+ var m = this.model;
216
+ var data = {};
217
+ $(m.attributes).each(function(i, a) {
218
+ data[a.name] = a.form_value();
219
+ });
220
+ return data;
221
+ },
222
+
223
+ // Show a confirmation screen
224
+ confirm: function(params)
225
+ {
226
+ var this2 = this;
227
+ $('#' + this.message).empty()
228
+ .append($('<div/>')
229
+ .addClass('note2 error')
230
+ .append($('<p/>').append(params.message))
231
+ .append($('<p/>')
232
+ .append($('<input />').attr('type', 'button').val('Yes').click(params.yes))
233
+ .append(" ")
234
+ .append($('<input />').attr('type', 'button').val('No').click(function() { $('#' + this2.message).empty(); }))
235
+ )
236
+ );
237
+ },
238
+
239
+ ajax_success: function(resp)
240
+ {
241
+ if (resp.text) $('#' + this.message).empty().append($('<p/>').addClass('note' ).html(resp.text));
242
+ else if (resp.success) $('#' + this.message).empty().append($('<p/>').addClass('note success').html(resp.success));
243
+ else if (resp.error) $('#' + this.message).empty().append($('<p/>').addClass('note error' ).html(resp.error));
244
+ else if (resp.flash) $('#' + this.message).empty().append($('<p/>').html(resp.flash).delay(2000).fadeOut().delay(2000).queue(function() { $(this).remove(); }));
245
+ else if (resp.redirect) window.location = resp.redirect;
246
+ else $('#' + this.message).empty();
247
+ },
248
+
249
+ ajax_error: function(str) { this.error(str); },
250
+
251
+ // Notes
252
+ loading: function(str) { this.show_message('loading' , str); },
253
+ note: function(str) { this.show_message('note' , str); },
254
+ success: function(str) { this.show_message('note success' , str); },
255
+ error: function(str) { this.show_message('note error' , str); },
256
+ show_message: function(class_names, str) { $('#' + this.message).empty().append($('<p/>').addClass(class_names).html(str)); }
257
+ });