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.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,143 @@
1
+ model.js
2
+ ========
3
+
4
+ Javascript library to handle CRUD actions for models via ajax.
5
+
6
+ Contains a model object, generic attribute object, and many common attribute types:
7
+
8
+ - checkbox-multiple
9
+ - checkbox
10
+ - date-time
11
+ - file
12
+ - image
13
+ - password
14
+ - radio
15
+ - rich-text
16
+ - select
17
+ - texarea
18
+ - textjs
19
+ - time
20
+
21
+ Adding new attribute types is simple. Just create a new class that extends Model.Attribute and override the following methods:
22
+
23
+ - view: Returns a viewable representation of the attribute.
24
+ - form: Returns an editable form of the attribute.
25
+
26
+ Example of a new attribute type:
27
+ <pre>
28
+ Model.Attribute.MyNewAttribute = Model.Attribute.extend({
29
+ view: function() {
30
+ return $('&lt;a/&gt;').html(this.value);
31
+ },
32
+ form: function() {
33
+ return $('&lt;form/&gt;').append($('&lt;input/&lt;').attr('type', 'text').attr('id', this.base).val(this.value));
34
+ }
35
+ });
36
+ </pre>
37
+
38
+ How to include on your site separately:
39
+ <pre>
40
+ &lt;script src="/assets/jquery.js" type="text/javascript"&gt;&lt;/script&gt;
41
+ &lt;script src="/assets/jquery-ui.js" type="text/javascript"&gt;&lt;/script&gt;
42
+ &lt;script src="/assets/class.js" type="text/javascript"&gt;&lt;/script&gt;
43
+ &lt;script src="/assets/model/model.js" type="text/javascript"&gt;&lt;/script&gt;
44
+ &lt;script src="/assets/model/attribute.js" type="text/javascript"&gt;&lt;/script&gt;
45
+ &lt;script src="/assets/model/form.js" type="text/javascript"&gt;&lt;/script&gt;
46
+ &lt;script src="/assets/model/attribute/checkbox-multiple.js" type="text/javascript"&gt;&lt;/script&gt;
47
+ &lt;script src="/assets/model/attribute/checkbox.js" type="text/javascript"&gt;&lt;/script&gt;
48
+ &lt;script src="/assets/model/attribute/date-time.js" type="text/javascript"&gt;&lt;/script&gt;
49
+ &lt;script src="/assets/model/attribute/file.js" type="text/javascript"&gt;&lt;/script&gt;
50
+ &lt;script src="/assets/model/attribute/image.js" type="text/javascript"&gt;&lt;/script&gt;
51
+ &lt;script src="/assets/model/attribute/password.js" type="text/javascript"&gt;&lt;/script&gt;
52
+ &lt;script src="/assets/model/attribute/radio.js" type="text/javascript"&gt;&lt;/script&gt;
53
+ &lt;script src="/assets/model/attribute/rich-text.js" type="text/javascript"&gt;&lt;/script&gt;
54
+ &lt;script src="/assets/model/attribute/select.js" type="text/javascript"&gt;&lt;/script&gt;
55
+ &lt;script src="/assets/model/attribute/texarea.js" type="text/javascript"&gt;&lt;/script&gt;
56
+ &lt;script src="/assets/model/attribute/textjs.js" type="text/javascript"&gt;&lt;/script&gt;
57
+ &lt;script src="/assets/model/attribute/time.js" type="text/javascript"&gt;&lt;/script&gt;
58
+ </pre>
59
+
60
+ Example when adding a model:
61
+ <pre>
62
+ &lt;div id='user_new_container'&gt;&lt;/div&gt;
63
+ </pre><pre>
64
+ &lt;script type='text/javascript'&gt;
65
+ $(document).ready(function() {
66
+ user = new Model({
67
+ name: 'User',
68
+ id: 'new',
69
+ attributes: [
70
+ { name: 'username', type: 'text', value: '' }
71
+ ]
72
+ });
73
+ });
74
+ &lt;/script&gt;
75
+ </pre>
76
+
77
+ Example when editing a model:
78
+ <pre>
79
+ &lt;div id='user_27_container'&gt;&lt;/div&gt;
80
+ </pre><pre>
81
+ &lt;script type='text/javascript'&gt;
82
+ $(document).ready(function() {
83
+ user = new Model({
84
+ name: 'User',
85
+ id: 27,
86
+ attributes: [
87
+ { name: 'first_name' , type: 'text', value: "&lt;%= @user.first_name %&gt;" },
88
+ { name: 'last_name' , type: 'text', value: "&lt;%= @user.last_name %&gt;" },
89
+ { name: 'username' , type: 'text', value: "&lt;%= @user.username %&gt;" },
90
+ { name: 'email' , type: 'text', value: "&lt;%= @user.email %&gt;" },
91
+ { name: 'password' , type: 'password' },
92
+ {
93
+ name: 'roles',
94
+ type: 'checkbox-multiple',
95
+ value: [1, 14, 18],
96
+ text: "Managers, Clients, Assistants",
97
+ empty_text: '[No roles]',
98
+ multiple: true,
99
+ loading_message: 'Getting roles...',
100
+ options_url: '/roles/options'
101
+ },
102
+ {
103
+ name: 'pic',
104
+ type: 'image',
105
+ value: '',
106
+ update_url: '/users/27/update-pic'
107
+ },
108
+ {
109
+ name: 'resume',
110
+ type: 'file',
111
+ value: '',
112
+ update_url: '/users/27/update-resume'
113
+ }
114
+ ]
115
+ });
116
+ });
117
+ &lt;/script&gt;
118
+ </pre>
119
+
120
+ <h2>Update Responses</h2>
121
+
122
+ For attributes that don't require a file upload, Model.js expects the server to respond with the following json object:
123
+
124
+ <pre>
125
+ {
126
+ success: true, // Whether or not the save was successful
127
+ error: false, // Any error to display to the user
128
+ attribute: {} // Any attribute values to set in the local attribute object
129
+ }
130
+ </pre>
131
+
132
+ The File and Image attributes allow for file uploads. Since a typical ajax call can't upload files, this is done with a hidden iframe. Because of this, the response required for a file or image update is the following:
133
+
134
+ <pre>
135
+ parent.Model.upload_finished({
136
+ name: 'user', // The name of the model
137
+ id: 27, // The id of the model
138
+ attribute_name: 'pic', // The name of the attribute
139
+ success: true, // Whether or not the save was successful
140
+ error: false, // Any error to display to the user
141
+ attribute: {} // Any attribute values to set in the local attribute object
142
+ });
143
+ </pre>
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Caboose'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
30
+ require 'rake/testtask'
31
+
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib'
34
+ t.libs << 'test'
35
+ t.pattern = 'test/**/*_test.rb'
36
+ t.verbose = false
37
+ end
38
+
39
+
40
+ task :default => :test
@@ -0,0 +1,72 @@
1
+
2
+ Model.Attribute.CheckboxMultiple = Model.Attribute.extend({
3
+
4
+ text: '',
5
+ show_controls: true,
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
+ .html(html);
19
+ },
20
+
21
+ form: function()
22
+ {
23
+ var tbody = $('<tbody/>');
24
+ var this2 = this;
25
+ var options = Model.get_attribute_options(this.model.name, this.name);
26
+ $.each(options, function(i, option) {
27
+ var cb = $('<input/>')
28
+ .attr('name', this2.base)
29
+ .attr('id', this2.base + '_' + i)
30
+ .attr('type', 'checkbox')
31
+ .val(option.value);
32
+ if (this2.in_value(option.value))
33
+ cb.attr('checked', true);
34
+
35
+ tbody.append($('<tr/>')
36
+ .append($('<td/>').append(cb))
37
+ .append($('<td/>')
38
+ .append($('<label/>')
39
+ .attr('for', this2.base + '_' + i)
40
+ .html(option.text)
41
+ ))
42
+ );
43
+ });
44
+ return $('<form/>').append($('<table/>').append(tbody));
45
+ },
46
+
47
+ form_value: function()
48
+ {
49
+ val = [];
50
+ $('#' + this.container + ' input[type=checkbox]:checked').each(function(i, checkbox) {
51
+ val[val.length] = $(checkbox).val();
52
+ });
53
+ return val;
54
+ },
55
+
56
+ in_value: function(val)
57
+ {
58
+ var count = this.value.count;
59
+ for (var v in this.value)
60
+ if (val == this.value[v])
61
+ return true;
62
+ return false;
63
+ },
64
+
65
+ needs_options: function()
66
+ {
67
+ if (!this.options)
68
+ return true;
69
+ return false;
70
+ }
71
+
72
+ });
@@ -0,0 +1,32 @@
1
+
2
+ Model.Attribute.Checkbox = Model.Attribute.extend({
3
+
4
+ show_controls: true,
5
+
6
+ view: function()
7
+ {
8
+ return $('<a/>')
9
+ .attr('title', 'Click to edit')
10
+ .attr('href', 'javascript:{};')
11
+ .addClass('model_attribute_text')
12
+ .html(this.value == 1 ? 'Yes' : 'No');
13
+ },
14
+
15
+ form: function()
16
+ {
17
+ return $('<form/>')
18
+ .append($('<input/>')
19
+ .attr('id', this.base)
20
+ .attr('name', this.base)
21
+ .attr('type', 'checkbox')
22
+ .val('1')
23
+ .attr('checked', this.value == 1)
24
+ );
25
+ },
26
+
27
+ form_value: function()
28
+ {
29
+ return $('#' + this.base).is(':checked') ? 1 : 0;
30
+ }
31
+
32
+ });
@@ -0,0 +1,30 @@
1
+
2
+ Model.Attribute.DateTime = 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
+ .html(html);
11
+ },
12
+
13
+ form: function()
14
+ {
15
+ return $('<form/>')
16
+ .append($('<input/>')
17
+ .attr('type', 'text')
18
+ .attr('id', this.base)
19
+ .attr('name', this.base)
20
+ .val(this.value)
21
+ .addClass('text_box')
22
+ );
23
+ },
24
+
25
+ post_form_display: function()
26
+ {
27
+ $('#' + this.base).datetimepicker({ ampm: true });
28
+ }
29
+
30
+ });
@@ -0,0 +1,88 @@
1
+
2
+ Model.Attribute.File = Model.Attribute.extend({
3
+
4
+ custom_form: true, // Tells the model to not do the traditional ajax_update.
5
+ // This means we control the entire update process
6
+
7
+ view: function()
8
+ {
9
+ return $('<a/>')
10
+ .attr('href', 'javascript:{};')
11
+ .html('[Upload new file]');
12
+ },
13
+
14
+ post_view_display: function()
15
+ {
16
+ if (!this.value || this.value.length == 0)
17
+ {
18
+ $('#' + this.container).append("No file has been uploaded.");
19
+ return;
20
+ }
21
+ $('#' + this.container).append($('<a/>')
22
+ .attr('href', this.value)
23
+ .html('[Download file]')
24
+ );
25
+ },
26
+
27
+ form: function()
28
+ {
29
+ var form = $('<form/>')
30
+ .attr('action', this.update_url)
31
+ .attr('method', 'post')
32
+ .attr('enctype', 'multipart/form-data')
33
+ .attr('encoding', 'multipart/form-data')
34
+ .attr('target', this.base + '_iframe');
35
+
36
+ // Add the csrf-token
37
+ form.append($('<input/>')
38
+ .attr('type', 'hidden')
39
+ .attr('name', 'authenticity_token')
40
+ .val($("meta[name=csrf-token]").attr('content'))
41
+ );
42
+
43
+ form.append($('<input />')
44
+ .attr('type', 'file')
45
+ .attr('id', this.base)
46
+ .attr('name', this.base)
47
+ );
48
+
49
+ this2 = this;
50
+ var controls = $('<span/>')
51
+ .attr('id', this.controls)
52
+ .append($('<input/>').attr('type', 'submit').val('Update').addClass('update_btn').click(function() { this2.loading('Uploading...'); }))
53
+ .append($('<input/>').attr('type', 'button').val('Cancel').addClass('cancel_btn').click(function() { this2.model.show_attribute(this2); }));
54
+ form.append(controls);
55
+ form.append($('<span/>').attr('id', attrib.message));
56
+ return form;
57
+ },
58
+
59
+ post_form_display: function()
60
+ {
61
+ // Create the iframe
62
+ $('#' + this.container).append($('<iframe></iframe>')
63
+ .attr('id', this.base + '_iframe')
64
+ .attr('name', this.base + '_iframe')
65
+ .css('width', '0')
66
+ .css('height', '0')
67
+ .css('border', '0')
68
+ //.css('width', 600)
69
+ //.css('height', 400)
70
+ //.css('border', '#000 1px solid')
71
+ );
72
+ },
73
+
74
+ upload_finished: function(resp)
75
+ {
76
+ if (resp.error)
77
+ {
78
+ this.error(resp.error);
79
+ }
80
+ if (resp.success)
81
+ {
82
+ if (resp.attribute)
83
+ for (var thing in resp.attribute)
84
+ attrib[thing] = resp.attribute[thing];
85
+ this.model.show_attribute(this);
86
+ }
87
+ }
88
+ });
@@ -0,0 +1,19 @@
1
+
2
+ Model.Attribute.Hidden = Model.Attribute.extend({
3
+
4
+ view: function()
5
+ {
6
+ return false;
7
+ },
8
+
9
+ form: function()
10
+ {
11
+ return false;
12
+ },
13
+
14
+ form_value: function()
15
+ {
16
+ return this.value;
17
+ }
18
+
19
+ });
@@ -0,0 +1,105 @@
1
+
2
+ Model.Attribute.Image = Model.Attribute.extend({
3
+
4
+ show_thumb: true,
5
+ thumb_width: 100,
6
+ custom_form: true, // Tells the model to not do the traditional ajax_update.
7
+ // This means we control the entire update process
8
+
9
+ view: function()
10
+ {
11
+ return $('<a/>')
12
+ .attr('href', 'javascript:{};')
13
+ .html('[Upload new image]');
14
+ },
15
+
16
+ post_view_display: function()
17
+ {
18
+ if (!this.show_thumb)
19
+ return;
20
+
21
+ if (!this.value || this.value.length == 0)
22
+ {
23
+ $('#' + this.container).append("No image has been uploaded.");
24
+ return;
25
+ }
26
+ $('#' + this.container).append($('<p/>')
27
+ .append($('<img />')
28
+ .attr('src', this.value + '?' + Math.random())
29
+ .attr('width', this.thumb_width)
30
+ )
31
+ );
32
+ },
33
+
34
+ form: function()
35
+ {
36
+ var form = $('<form/>')
37
+ .attr('action', this.update_url)
38
+ .attr('method', 'post')
39
+ .attr('enctype', 'multipart/form-data')
40
+ .attr('encoding', 'multipart/form-data')
41
+ .attr('target', this.base + '_iframe');
42
+
43
+ // Add the csrf-token
44
+ form.append($('<input/>')
45
+ .attr('type', 'hidden')
46
+ .attr('name', 'authenticity_token')
47
+ .val($("meta[name=csrf-token]").attr('content'))
48
+ );
49
+
50
+ if (this.show_thumb)
51
+ {
52
+ form.append($('<p/>')
53
+ .append($('<img />')
54
+ .attr('src', this.value + '?' + Math.random())
55
+ .attr('width', this.thumb_width)
56
+ ));
57
+ }
58
+
59
+ form.append($('<input />')
60
+ .attr('type', 'file')
61
+ .attr('id', this.base)
62
+ .attr('name', this.base)
63
+ );
64
+
65
+ this2 = this;
66
+ var controls = $('<span/>')
67
+ .attr('id', this.controls)
68
+ .append($('<input/>').attr('type', 'submit').val('Update').addClass('update_btn').click(function() { this2.loading('Uploading...'); }))
69
+ .append($('<input/>').attr('type', 'button').val('Cancel').addClass('cancel_btn').click(function() { this2.model.show_attribute(this2); }));
70
+ form.append(controls);
71
+ form.append($('<span/>').attr('id', attrib.message));
72
+ return form;
73
+ },
74
+
75
+ post_form_display: function()
76
+ {
77
+ // Create the iframe
78
+ $('#' + this.container).append($('<iframe></iframe>')
79
+ .attr('id', this.base + '_iframe')
80
+ .attr('name', this.base + '_iframe')
81
+ .css('width', '0')
82
+ .css('height', '0')
83
+ .css('border', '0')
84
+ //.css('width', 600)
85
+ //.css('height', 400)
86
+ //.css('border', '#000 1px solid')
87
+ );
88
+ },
89
+
90
+ upload_finished: function(resp)
91
+ {
92
+ if (resp.error)
93
+ {
94
+ this.error(resp.error);
95
+ }
96
+ if (resp.success)
97
+ {
98
+ if (resp.attribute)
99
+ for (var thing in resp.attribute)
100
+ attrib[thing] = resp.attribute[thing];
101
+ this.model.show_attribute(this);
102
+ }
103
+ }
104
+
105
+ });
@@ -0,0 +1,36 @@
1
+
2
+ Model.Attribute.Password = Model.Attribute.extend({
3
+
4
+ view: function()
5
+ {
6
+ return $('<a/>')
7
+ .attr('title', 'Click to edit')
8
+ .attr('href', 'javascript:{};')
9
+ .html('Change Password');
10
+ },
11
+
12
+ form: function()
13
+ {
14
+ return $('<form/>')
15
+ .append($('<input/>')
16
+ .attr('type', 'password')
17
+ .attr('id', this.base)
18
+ .attr('name', this.base)
19
+ )
20
+ .append($('<br/>'))
21
+ .append($('<input/>')
22
+ .attr('type', 'password')
23
+ .attr('id', this.base + '_confirm')
24
+ .attr('name', this.base + '_confirm')
25
+ )
26
+ .append($('<span> (Confirm)</span>'));
27
+ },
28
+
29
+ form_values: function()
30
+ {
31
+ values = this._super();
32
+ values.confirm = $('#' + this.base + '_confirm').val();
33
+ return values;
34
+ }
35
+
36
+ });
@@ -0,0 +1,60 @@
1
+
2
+ Model.Attribute.Radio = Model.Attribute.extend({
3
+
4
+ text: '',
5
+ show_controls: true,
6
+
7
+ needs_options: function()
8
+ {
9
+ return true;
10
+ },
11
+
12
+ view: function()
13
+ {
14
+ 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_text')
19
+ .html(html);
20
+ },
21
+
22
+ form: function()
23
+ {
24
+ var tbody = $('<tbody/>');
25
+ var this2 = this;
26
+ var options = Model.get_attribute_options(this.model.name, this.name);
27
+ $.each(options, function(i, option) {
28
+ tbody.append($('<tr/>')
29
+ .append($('<td/>')
30
+ .append($('<input/>')
31
+ .attr('name', this2.base)
32
+ .attr('id', this2.base + '_' + i)
33
+ .attr('type', 'radio')
34
+ .val(option.value)
35
+ ))
36
+ .append($('<td/>')
37
+ .append($('<label/>')
38
+ .attr('for', this2.base + '_' + i)
39
+ .html(option.text)
40
+ ))
41
+ );
42
+ });
43
+ return $('<form/>').append($('<table/>').append(tbody));
44
+ },
45
+
46
+ form_value: function()
47
+ {
48
+ value = '';
49
+ $('#' + this.base + '_form input:radio').each(function(i, r) {
50
+ if (r.checked)
51
+ {
52
+ value = r.val();
53
+ return false;
54
+ }
55
+ });
56
+ return value;
57
+ /* Note: if for some reason a value is not selected, value becomes empty. */
58
+ }
59
+
60
+ });
@@ -0,0 +1,40 @@
1
+
2
+ Model.Attribute.RichText = Model.Attribute.extend({
3
+
4
+ show_controls: true,
5
+
6
+ view: function()
7
+ {
8
+ tinyMCE.execCommand('mceRemoveControl', null, $('#' + this.base));
9
+ var html = this.value && this.value.length > 0 ? this.value : this.empty_text;
10
+ return $('<div/>')
11
+ .addClass('model_attribute_container')
12
+ .html(html);
13
+ },
14
+
15
+ form: function()
16
+ {
17
+ return $('<form/>')
18
+ .append($('<textarea/>')
19
+ .addClass('mceEditor')
20
+ .attr('id', this.base)
21
+ .attr('name', this.base)
22
+ .val(this.value)
23
+ );
24
+ },
25
+
26
+ form_value: function()
27
+ {
28
+ tinyMCE.triggerSave();
29
+ tinyMCE.execCommand('mceRemoveControl', null, $('#' + this.base));
30
+ return $('#' + this.base).val();
31
+ },
32
+
33
+ post_form_display: function()
34
+ {
35
+ $('#' + this.base).css('display', 'block');
36
+ tinyMCE.execCommand('mceAddControl', null, $('#' + this.base));
37
+ $('#' + this.base).focus();
38
+ }
39
+
40
+ });