vue_crud 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/README.md +36 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/generators/vue_crud/install_generator.rb +11 -0
- data/lib/generators/vue_crud/templates/vue_crud.html +221 -0
- data/lib/vue_crud/engine.rb +4 -0
- data/lib/vue_crud/version.rb +3 -0
- data/lib/vue_crud.rb +6 -0
- data/vendor/assets/javascripts/vue_crud.js +295 -0
- data/vendor/assets/stylesheets/vue_crud.css +19 -0
- data/vue_crud.gemspec +35 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7a3968959d978911425d23ccc57bad7a6e520b1d
|
4
|
+
data.tar.gz: e22d398e429375cb72eb45068d691f8e8db1201d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 68fcb6ff3f771628a6d82db744af6e76d87b9e143dea98ab6736a9412d0cd59a82ecc938279c0098fe1e73d6bf43dffea411d7dba191dc6f6e2f0ca1fe478fe2
|
7
|
+
data.tar.gz: b57aad62dbf33091b101f4128cabb298a8e73952fa28570a9b7504dbe174c8cd67cd6a19b7020b28d4104ca0cea5a9ce1a5934d6c32f0f6a08e9975a39e3868a
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# VueCrud
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/vue_crud`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'vue_crud'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install vue_crud
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/vue_crud.
|
36
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "vue_crud"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#lib/generators/gemname/install_generator.rb
|
2
|
+
require 'rails/generators'
|
3
|
+
module VueCrud
|
4
|
+
class InstallGenerator < Rails::Generators::Base
|
5
|
+
source_root File.expand_path("../templates", __FILE__)
|
6
|
+
|
7
|
+
def copy_template_file
|
8
|
+
copy_file "vue_crud.html", "public/vue_crud.html"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
<script type="text/javascript">
|
2
|
+
$(document).ready(function() {
|
3
|
+
$('.vue_form_for_new').html($('#vue_form').html());
|
4
|
+
var model_row = $($('#model-row').html());
|
5
|
+
model_row.find('.custom_model_row').html(
|
6
|
+
$('#custom_model_row').html()
|
7
|
+
);
|
8
|
+
model_row.find('.custom_action').html(
|
9
|
+
$('#custom_action').html()
|
10
|
+
);
|
11
|
+
model_row.find('.vue_form_for_edit').html(
|
12
|
+
$('#vue_form').html()
|
13
|
+
);
|
14
|
+
model_row.find('.custom_modal_body').html(
|
15
|
+
$('#custom_modal_body').html()
|
16
|
+
);
|
17
|
+
model_row.find('.custom_modal_action').html(
|
18
|
+
$('#custom_modal_action').html()
|
19
|
+
);
|
20
|
+
|
21
|
+
$('#model-row').html(model_row);
|
22
|
+
vue_init();
|
23
|
+
$('.filter .ui.dropdown').dropdown();
|
24
|
+
});
|
25
|
+
</script>
|
26
|
+
|
27
|
+
|
28
|
+
<div id="models">
|
29
|
+
<input type="text" name="progress" id="form-progress">
|
30
|
+
<h1 class="ui header">
|
31
|
+
{{ info.titles.title }}
|
32
|
+
<div class="sub header"><small>{{ info.titles.subtitle }}</small></div>
|
33
|
+
</h1>
|
34
|
+
<div class="ui blue button" data-modal="#modelModal">Add</div>
|
35
|
+
<div class="ui divider"></div>
|
36
|
+
<!-- NOTE: Filter -->
|
37
|
+
<div class="ui left action right icon labeled input filter" :class="{loading: is_calculating}">
|
38
|
+
<a :href="info.url_prefix" class="ui button red" v-show="selected_attribute">Reset</a>
|
39
|
+
<div class="ui basic floating dropdown button">
|
40
|
+
<div class="text">{{ selected_attribute || 'Attribute' }}</div>
|
41
|
+
<i class="dropdown icon"></i>
|
42
|
+
<div class="menu attribute_select">
|
43
|
+
<div class="item attribute_option" v-for="attribute in info.model_attributes" :model_prefix="attribute.model_prefix || ''" :target="attribute.name" :class="{selected: selected_attribute == attribute.name,active: selected_attribute == attribute.name}">{{ attribute.display_name }}</div>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
<input type="text" id="searchQuery" :placeholder="searchStatus" v-model="searchQuery">
|
47
|
+
<i class="icon search"></i>
|
48
|
+
</div>
|
49
|
+
<div class="ui divider"></div>
|
50
|
+
<!-- NOTE: Pagination -->
|
51
|
+
<div class="ui pagination menu">
|
52
|
+
<a class="item" @click.prevent="setPage(currentPage-1)" :class="{'disabled': (currentPage == '1')}">
|
53
|
+
<
|
54
|
+
</a>
|
55
|
+
<a v-for="n in totalPage" @click.prevent="setPage(n)" :class="{'active': (currentPage == (n))}" class="item">
|
56
|
+
{{n}}
|
57
|
+
</a>
|
58
|
+
<a class="item" @click.prevent="setPage(currentPage+1)" :class="{'disabled': (currentPage == totalPage)}">
|
59
|
+
>
|
60
|
+
</a>
|
61
|
+
</div>
|
62
|
+
<div class="ui divider"></div>
|
63
|
+
<!-- NOTE: model-row -->
|
64
|
+
<div class="row ui cards">
|
65
|
+
<model-row is="model-row" v-for="model in filteredModels" :model="model" :info="info" :selected_attribute="model_prefix + '.' + selected_attribute"></model-row>
|
66
|
+
</div>
|
67
|
+
|
68
|
+
<!-- NOTE: modal for new action -->
|
69
|
+
<div class="ui modal new_modal" id="modelModal">
|
70
|
+
<div class="header">Create {{ info.titles.title }}</div>
|
71
|
+
<!-- NOTE: Vue form -->
|
72
|
+
<div class="content vue_form_for_new"></div>
|
73
|
+
<div class="actions">
|
74
|
+
<div class="ui basic gray cancel button">Cancel</div>
|
75
|
+
<div @click="createModel" class="ui primary button">Create</div>
|
76
|
+
</div>
|
77
|
+
<div class="ui blue bottom attached progress">
|
78
|
+
<div class="bar" :style="{width: progress + '%'}"></div>
|
79
|
+
</div>
|
80
|
+
</div>
|
81
|
+
</div>
|
82
|
+
|
83
|
+
<!-- NOTE: Templates -->
|
84
|
+
<script type="text/x-template" id="model-row">
|
85
|
+
<div class="col-md-4">
|
86
|
+
<div class="card">
|
87
|
+
<div class="content">
|
88
|
+
<div class="header" v-if="info.titles.title_attribute_model_prefix">{{ model[info.titles.title_attribute_model_prefix][info.titles.title_attribute] }}</div>
|
89
|
+
<div class="header" v-else>{{ model[info.titles.title_attribute] }}</div>
|
90
|
+
<div class="meta">{{ info.titles.model_name }}</div>
|
91
|
+
<div class="ui divider"></div>
|
92
|
+
<div class="description">
|
93
|
+
<div class="ui small feed" v-for="attribute in info.model_attributes" v-if="attribute.visible">
|
94
|
+
<div class="event">
|
95
|
+
<div class="content">
|
96
|
+
<div class="summary" :class="{highlight: attribute.model_prefix + '.' + attribute.name == selected_attribute}" v-if="attribute.model_prefix">
|
97
|
+
{{ attribute.display_name }}: {{ model[attribute.model_prefix][attribute.name] }}
|
98
|
+
</div>
|
99
|
+
<div class="summary" :class="{highlight: attribute.model_prefix + '.' + attribute.name == selected_attribute}" v-else>
|
100
|
+
{{ attribute.display_name }}: {{ model[attribute.name] }}
|
101
|
+
</div>
|
102
|
+
</div>
|
103
|
+
</div>
|
104
|
+
</div>
|
105
|
+
<!-- NOTE: Custom model row -->
|
106
|
+
<div class="custom_model_row"></div>
|
107
|
+
</div>
|
108
|
+
</div>
|
109
|
+
<div class="extra content">
|
110
|
+
<div class="ui orange right ribbon label" v-show="modified">您有尚未儲存的變更,<a href="#" @click="updateModel(false)" onclick="return false" class="save_now">立即儲存</a></div>
|
111
|
+
<div class="ui teal right ribbon label" v-show="is_success">儲存成功</div>
|
112
|
+
<div class="ui divider"></div>
|
113
|
+
<!-- NOTE: Custom action -->
|
114
|
+
<div class="inline-block custom_action"></div>
|
115
|
+
<div class="ui blue button" :data-modal="'#editModal_' + model.id">Edit</div>
|
116
|
+
<div @click="deleteModel" class="ui basic red right floated button" v-show="!deleteMode">Delete</div>
|
117
|
+
</div>
|
118
|
+
<div class="ui modal" :id="'editModal_' + model.id">
|
119
|
+
<div class="ui inverted dimmer">
|
120
|
+
<div class="ui indeterminate text loader">Progressing</div>
|
121
|
+
</div>
|
122
|
+
<div class="header">Update {{ info.titles.title }}</div>
|
123
|
+
<div class="content">
|
124
|
+
<!-- NOTE: Not customMode -->
|
125
|
+
<div v-show="!customMode" class="vue_form_for_edit"></div>
|
126
|
+
<!-- NOTE: Is customMode -->
|
127
|
+
<!-- NOTE: Custom modal body -->
|
128
|
+
<div v-show="customMode"><div class="custom_modal_body"></div></div>
|
129
|
+
</div>
|
130
|
+
<div class="actions">
|
131
|
+
<button class="ui basic gray cancel button">Cancel</button>
|
132
|
+
<button @click="updateModel" class="ui button blue" v-show="!customMode">Update</button>
|
133
|
+
<!-- NOTE: Custom modal action -->
|
134
|
+
<div class="inline-block custom_modal_action"></div>
|
135
|
+
</div>
|
136
|
+
</div>
|
137
|
+
<div class="ui blue bottom attached progress">
|
138
|
+
<div class="bar" :style="{width: progress + '%'}"></div>
|
139
|
+
</div>
|
140
|
+
</div>
|
141
|
+
</div>
|
142
|
+
</script>
|
143
|
+
|
144
|
+
<script type="text/x-template" id="vue_form">
|
145
|
+
<form class="ui form">
|
146
|
+
<div v-for="attribute in info.model_attributes" v-if="attribute.editable">
|
147
|
+
<div class="field" v-if="attribute.type == 'select'">
|
148
|
+
<label :for="'model_' + attribute.display_name">{{ attribute.display_name }}</label>
|
149
|
+
<select :id="'model_' + attribute.display_name" v-if="attribute.model_prefix" v-model="model[attribute.model_prefix][attribute.name]" class="ui fluid dropdown">
|
150
|
+
<option v-for="option in attribute.options" :value="option.value" v-text="option.text"></option>
|
151
|
+
</select>
|
152
|
+
<select :id="'model_' + attribute.display_name" v-else v-model="model[attribute.name]" class="ui fluid dropdown">
|
153
|
+
<option v-for="option in attribute.options" :value="option.value" v-text="option.text"></option>
|
154
|
+
</select>
|
155
|
+
<div class="ui pointing red basic label" v-show="errors[attribute.name]">
|
156
|
+
<span style="color: red" v-for="(error, index) in errors[attribute.name]">
|
157
|
+
<span v-if="index > 0">,</span> {{ error }}
|
158
|
+
</span>
|
159
|
+
</div>
|
160
|
+
</div>
|
161
|
+
<div class="field" v-if="attribute.type == 'multiSelect'">
|
162
|
+
<label :for="'model_' + attribute.display_name">{{ attribute.display_name }}</label>
|
163
|
+
<select :id="'model_' + attribute.display_name" multiple v-if="attribute.model_prefix" v-model="model[attribute.model_prefix][attribute.name]" class="ui fluid dropdown">
|
164
|
+
<option v-for="option in attribute.options" :value="option.value" v-text="option.text"></option>
|
165
|
+
</select>
|
166
|
+
<select :id="'model_' + attribute.display_name" multiple v-else v-model="model[attribute.name]" class="ui fluid dropdown">
|
167
|
+
<option v-for="option in attribute.options" :value="option.value" v-text="option.text"></option>
|
168
|
+
</select>
|
169
|
+
<div class="ui pointing red basic label" v-show="errors[attribute.name]">
|
170
|
+
<span style="color: red" v-for="(error, index) in errors[attribute.name]">
|
171
|
+
<span v-if="index > 0">,</span> {{ error }}
|
172
|
+
</span>
|
173
|
+
</div>
|
174
|
+
</div>
|
175
|
+
<div class="field" v-if="attribute.type == 'textarea'">
|
176
|
+
<label :for="'model_' + attribute.display_name">{{ attribute.display_name }}</label>
|
177
|
+
<textarea :id="'model_' + attribute.display_name" v-if="attribute.model_prefix" v-model="model[attribute.model_prefix][attribute.name]"></textarea>
|
178
|
+
<textarea :id="'model_' + attribute.display_name" v-else v-model="model[attribute.name]"></textarea>
|
179
|
+
<div class="ui pointing red basic label" v-show="errors[attribute.name]">
|
180
|
+
<span style="color: red" v-for="(error, index) in errors[attribute.name]">
|
181
|
+
<span v-if="index > 0">,</span> {{ error }}
|
182
|
+
</span>
|
183
|
+
</div>
|
184
|
+
</div>
|
185
|
+
<div class="field" v-if="attribute.type == 'checkbox'">
|
186
|
+
<div class="ui slider checkbox">
|
187
|
+
<input type="checkbox" :id="'model_' + attribute.display_name" v-if="attribute.model_prefix" v-model="model[attribute.model_prefix][attribute.name]">
|
188
|
+
<input type="checkbox" :id="'model_' + attribute.display_name" v-else v-model="model[attribute.name]">
|
189
|
+
<label :for="'model_' + attribute.display_name">{{ attribute.display_name }}</label>
|
190
|
+
</div>
|
191
|
+
<div class="ui pointing red basic label" v-show="errors[attribute.name]">
|
192
|
+
<span style="color: red" v-for="(error, index) in errors[attribute.name]">
|
193
|
+
<span v-if="index > 0">,</span> {{ error }}
|
194
|
+
</span>
|
195
|
+
</div>
|
196
|
+
</div>
|
197
|
+
<div class="field" v-if="attribute.type != 'select' && attribute.type != 'multiSelect' && attribute.type != 'textarea' && attribute.type != 'checkbox'">
|
198
|
+
<label :for="'model_' + attribute.display_name">{{ attribute.display_name }}</label>
|
199
|
+
<input :type="attribute.type" :id="'model_' + attribute.display_name" v-if="attribute.model_prefix" v-model="model[attribute.model_prefix][attribute.name]">
|
200
|
+
<input :type="attribute.type" :id="'model_' + attribute.display_name" v-else v-model="model[attribute.name]">
|
201
|
+
<div class="ui pointing red basic label" v-show="errors[attribute.name]">
|
202
|
+
<span style="color: red" v-for="(error, index) in errors[attribute.name]">
|
203
|
+
<span v-if="index > 0">,</span> {{ error }}
|
204
|
+
</span>
|
205
|
+
</div>
|
206
|
+
</div>
|
207
|
+
</div>
|
208
|
+
</form>
|
209
|
+
</script>
|
210
|
+
<!-- NOTE: memo-->
|
211
|
+
<!-- <div class="row well well-sm">
|
212
|
+
<model-row is="model-row" v-for="model in models" :model="model"></model-row>
|
213
|
+
</div> -->
|
214
|
+
<!-- ┌───────────────────────────────────────┐ -->
|
215
|
+
<!-- │ ↓ -->
|
216
|
+
<!-- v-for model in models 這個 model 會透過 :model="model" 傳值到 template,無法直接使用 v-for 迴圈出來的 model -->
|
217
|
+
<!-- │
|
218
|
+
In model-row component props: { ↓
|
219
|
+
model: Object
|
220
|
+
}
|
221
|
+
-->
|
data/lib/vue_crud.rb
ADDED
@@ -0,0 +1,295 @@
|
|
1
|
+
$(document).ajaxStart(function() { Pace.restart(); });
|
2
|
+
var suffix = '.json';
|
3
|
+
Vue.config.devtools = true;
|
4
|
+
Pace.on('done', function() {
|
5
|
+
$('#form-progress').val('0');
|
6
|
+
});
|
7
|
+
var event_hub = new Vue();
|
8
|
+
function sleep(time) {
|
9
|
+
return new Promise((resolve) => setTimeout(resolve, time));
|
10
|
+
}
|
11
|
+
function obj_to_json(obj) {
|
12
|
+
return JSON.parse(JSON.stringify(obj));
|
13
|
+
}
|
14
|
+
var model_row_mixin = {}
|
15
|
+
var root_mixin = {}
|
16
|
+
function updateQueryStringParam(key, value) {
|
17
|
+
baseUrl = [location.protocol, '//', location.host, location.pathname].join('');
|
18
|
+
urlQueryString = document.location.search;
|
19
|
+
var newParam = key + '=' + value,
|
20
|
+
params = '?' + newParam;
|
21
|
+
|
22
|
+
// If the "search" string exists, then build params from it
|
23
|
+
if (urlQueryString) {
|
24
|
+
keyRegex = new RegExp('([\?&])' + key + '[^&]*');
|
25
|
+
// If param exists already, update it
|
26
|
+
if (urlQueryString.match(keyRegex) !== null) {
|
27
|
+
params = urlQueryString.replace(keyRegex, "$1" + newParam);
|
28
|
+
} else { // Otherwise, add it to end of query string
|
29
|
+
params = urlQueryString + '&' + newParam;
|
30
|
+
}
|
31
|
+
}
|
32
|
+
window.history.replaceState({}, "", baseUrl + params);
|
33
|
+
}
|
34
|
+
function getParameterByName(name) {
|
35
|
+
var url = window.location.href;
|
36
|
+
name = name.replace(/[\[\]]/g, "\\$&");
|
37
|
+
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
38
|
+
results = regex.exec(url);
|
39
|
+
if (!results) return '';
|
40
|
+
if (!results[2]) return '';
|
41
|
+
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
42
|
+
}
|
43
|
+
function vue_init() {
|
44
|
+
Vue.filter('datetime', function (value) {
|
45
|
+
return new Date(value).toLocaleString();
|
46
|
+
})
|
47
|
+
|
48
|
+
var modelRowComponent = Vue.component('model-row', {
|
49
|
+
mixins: [model_row_mixin],
|
50
|
+
template: '#model-row',
|
51
|
+
props: {
|
52
|
+
model: Object,
|
53
|
+
selected_attribute: String,
|
54
|
+
info: Object
|
55
|
+
},
|
56
|
+
data: function() {
|
57
|
+
return {
|
58
|
+
progress: '0',
|
59
|
+
errors: {},
|
60
|
+
modified: false,
|
61
|
+
customMode: false,
|
62
|
+
deleteMode: false,
|
63
|
+
is_success: false
|
64
|
+
}
|
65
|
+
},
|
66
|
+
watch: {
|
67
|
+
model: {
|
68
|
+
handler: function (value, old_value) {
|
69
|
+
// old_value and value(model self) will be changed on Computed method
|
70
|
+
// Only watch model's data change so set value === old_value
|
71
|
+
// value === old_value means model not change
|
72
|
+
if(value === old_value) {
|
73
|
+
this.modified = true;
|
74
|
+
this.is_success = false;
|
75
|
+
}
|
76
|
+
},
|
77
|
+
deep: true
|
78
|
+
}
|
79
|
+
},
|
80
|
+
mounted: function() {
|
81
|
+
event_hub.$on('update_progress', function(progress) {
|
82
|
+
this.progress = progress;
|
83
|
+
}.bind(this));
|
84
|
+
|
85
|
+
event_hub.$on('cancel_customMode', function() {
|
86
|
+
this.customMode = false;
|
87
|
+
}.bind(this));
|
88
|
+
},
|
89
|
+
methods: {
|
90
|
+
deleteModel: function () {
|
91
|
+
var that = this;
|
92
|
+
that.deleteMode = true;
|
93
|
+
if(confirm("Are you sure?") == true) {
|
94
|
+
$.ajax({
|
95
|
+
method: 'DELETE',
|
96
|
+
url: that.info.url_prefix + '/' + that.model.id + suffix,
|
97
|
+
success: function(res) {
|
98
|
+
$(that.$el).remove();
|
99
|
+
},
|
100
|
+
error: function(res) {
|
101
|
+
that.deleteMode = false;
|
102
|
+
}
|
103
|
+
})
|
104
|
+
}
|
105
|
+
else {
|
106
|
+
that.deleteMode = false;
|
107
|
+
}
|
108
|
+
},
|
109
|
+
updateModel: function() {
|
110
|
+
var that = this;
|
111
|
+
var modal = $('#editModal_' + that.model.id);
|
112
|
+
$.ajax({
|
113
|
+
method: 'PATCH',
|
114
|
+
data: {
|
115
|
+
model: that.model,
|
116
|
+
},
|
117
|
+
url: that.info.url_prefix + '/' + that.model.id + suffix,
|
118
|
+
beforeSend: function() {
|
119
|
+
modal.find('.dimmer').toggleClass('active');
|
120
|
+
$('.progress-bar').css('width', '0');
|
121
|
+
},
|
122
|
+
success: function(res) {
|
123
|
+
that.errors = {};
|
124
|
+
that.modified = false;
|
125
|
+
that.is_success = true;
|
126
|
+
sleep(500).then(() => {
|
127
|
+
modal.find('.dimmer').toggleClass('active');
|
128
|
+
modal.modal('hide');
|
129
|
+
});
|
130
|
+
sleep(1500).then(() => {
|
131
|
+
that.is_success = false;
|
132
|
+
});
|
133
|
+
},
|
134
|
+
error: function(res) {
|
135
|
+
that.errors = res.responseJSON.errors;
|
136
|
+
modal.find('.dimmer').toggleClass('active');
|
137
|
+
}
|
138
|
+
});
|
139
|
+
}
|
140
|
+
}
|
141
|
+
})
|
142
|
+
|
143
|
+
var modelsVue = new Vue({
|
144
|
+
mixins: [root_mixin],
|
145
|
+
el: '#models',
|
146
|
+
data: {
|
147
|
+
models: [],
|
148
|
+
info: {
|
149
|
+
url_prefix: '',
|
150
|
+
titles: {},
|
151
|
+
structure: {},
|
152
|
+
model_attributes: []
|
153
|
+
},
|
154
|
+
model: {},
|
155
|
+
errors: {},
|
156
|
+
progress: '0',
|
157
|
+
countOfPage: 9,
|
158
|
+
currentPage: getParameterByName("p") || 1,
|
159
|
+
searchQuery: getParameterByName("q"),
|
160
|
+
filteredModelCount: null,
|
161
|
+
is_calculating: false,
|
162
|
+
model_prefix: getParameterByName("mp"),
|
163
|
+
selected_attribute: getParameterByName("sa")
|
164
|
+
},
|
165
|
+
mounted: function() {
|
166
|
+
var that = this;
|
167
|
+
$.ajax({
|
168
|
+
url: window.location.pathname + '/info' + suffix,
|
169
|
+
success: function(res) {
|
170
|
+
that.info = res;
|
171
|
+
that.model = obj_to_json(that.info.structure);
|
172
|
+
$.ajax({
|
173
|
+
url: that.info.url_prefix + suffix,
|
174
|
+
success: function(res) {
|
175
|
+
that.models = res;
|
176
|
+
}
|
177
|
+
});
|
178
|
+
}
|
179
|
+
});
|
180
|
+
event_hub.$on('update_progress', function(progress) {
|
181
|
+
this.progress = progress;
|
182
|
+
}.bind(this));
|
183
|
+
$('#form-progress').change(function(){
|
184
|
+
event_hub.$emit('update_progress', $(this).val());
|
185
|
+
});
|
186
|
+
$(document).delegate('.attribute_option', 'click', function(event) {
|
187
|
+
var model_prefix = $('.attribute_select').find('.item.selected').attr('model_prefix');
|
188
|
+
var selected_attribute = $('.attribute_select').find('.item.selected').attr('target') || "id";
|
189
|
+
that.model_prefix = model_prefix;
|
190
|
+
that.selected_attribute = selected_attribute;
|
191
|
+
updateQueryStringParam('mp', that.model_prefix);
|
192
|
+
updateQueryStringParam('sa', that.selected_attribute);
|
193
|
+
});
|
194
|
+
},
|
195
|
+
computed: {
|
196
|
+
searchStatus: function() {
|
197
|
+
if (this.is_calculating) {
|
198
|
+
return 'Loading...'
|
199
|
+
} else {
|
200
|
+
return '✓ Done';
|
201
|
+
}
|
202
|
+
},
|
203
|
+
pageStart: function() {
|
204
|
+
var that = this;
|
205
|
+
return (that.currentPage - 1) * that.countOfPage;
|
206
|
+
},
|
207
|
+
totalPage: function() {
|
208
|
+
var that = this;
|
209
|
+
if(that.searchQuery.trim() === '') {
|
210
|
+
return Math.ceil(that.models.length / that.countOfPage);
|
211
|
+
}
|
212
|
+
else{
|
213
|
+
return Math.ceil(that.filteredModelCount / that.countOfPage);
|
214
|
+
}
|
215
|
+
},
|
216
|
+
filteredModels: function() {
|
217
|
+
var that = this;
|
218
|
+
that.is_calculating = true;
|
219
|
+
var result = that.models.filter(function (model) {
|
220
|
+
if(that.model_prefix.length > 0) {
|
221
|
+
return String(model[that.model_prefix][that.selected_attribute]).indexOf(that.searchQuery) !== -1;
|
222
|
+
}
|
223
|
+
else {
|
224
|
+
return String(model[that.selected_attribute]).indexOf(that.searchQuery) !== -1;
|
225
|
+
}
|
226
|
+
});
|
227
|
+
that.filteredModelCount = result.length
|
228
|
+
setTimeout(function () {
|
229
|
+
that.is_calculating = false
|
230
|
+
}, 500)
|
231
|
+
return result.slice(
|
232
|
+
that.pageStart,
|
233
|
+
that.countOfPage + that.pageStart
|
234
|
+
);
|
235
|
+
}
|
236
|
+
},
|
237
|
+
watch: {
|
238
|
+
searchQuery: function () {
|
239
|
+
this.currentPage = 1;
|
240
|
+
updateQueryStringParam('q', this.searchQuery);
|
241
|
+
updateQueryStringParam('p', 1);
|
242
|
+
}
|
243
|
+
},
|
244
|
+
methods: {
|
245
|
+
setPage: function(idx){
|
246
|
+
if( idx <= 0 || idx > this.totalPage ){
|
247
|
+
return;
|
248
|
+
}
|
249
|
+
this.currentPage = idx;
|
250
|
+
updateQueryStringParam('p', this.currentPage);
|
251
|
+
},
|
252
|
+
createModel: function () {
|
253
|
+
var that = this;
|
254
|
+
$.ajax({
|
255
|
+
method: 'POST',
|
256
|
+
data: {
|
257
|
+
model: that.model,
|
258
|
+
},
|
259
|
+
url: that.info.url_prefix + suffix,
|
260
|
+
before: function() {
|
261
|
+
$('.new_modal').find('.dimmer').toggleClass('active');
|
262
|
+
$('.progress-bar').css('width', '0');
|
263
|
+
},
|
264
|
+
success: function(res) {
|
265
|
+
var modal = $(that.$el).parents('.modal');
|
266
|
+
that.errors = {};
|
267
|
+
that.model = obj_to_json(that.info.structure);
|
268
|
+
that.models.push(res);
|
269
|
+
sleep(500).then(() => {
|
270
|
+
$('.new_modal').find('.dimmer').toggleClass('active');
|
271
|
+
$('.new_modal').modal('hide');
|
272
|
+
});
|
273
|
+
},
|
274
|
+
error: function(res) {
|
275
|
+
that.errors = res.responseJSON.errors;
|
276
|
+
$('.new_modal').find('.dimmer').toggleClass('active');
|
277
|
+
}
|
278
|
+
});
|
279
|
+
}
|
280
|
+
}
|
281
|
+
})
|
282
|
+
}
|
283
|
+
$(document).ready(function() {
|
284
|
+
$(document).delegate('[data-modal]', 'click', function(event) {
|
285
|
+
var target = $(this).data('modal');
|
286
|
+
$(target).modal({
|
287
|
+
onShow: function() {
|
288
|
+
$(this).find('select.ui.dropdown').dropdown();
|
289
|
+
},
|
290
|
+
onHide: function() {
|
291
|
+
event_hub.$emit('cancel_customMode');
|
292
|
+
}
|
293
|
+
}).modal('show');
|
294
|
+
});
|
295
|
+
});
|
@@ -0,0 +1,19 @@
|
|
1
|
+
.ui.cards > .card > .extra a.save_now {
|
2
|
+
color: rgb(33, 133, 208);
|
3
|
+
opacity: 1;
|
4
|
+
}
|
5
|
+
.progress {
|
6
|
+
margin-bottom: 0;
|
7
|
+
border-radius: 0;
|
8
|
+
height: 10px;
|
9
|
+
}
|
10
|
+
#form-progress {
|
11
|
+
position: fixed;
|
12
|
+
top: -9999px;
|
13
|
+
}
|
14
|
+
.summary.highlight {
|
15
|
+
border: 2px solid #a90000;
|
16
|
+
}
|
17
|
+
.inline-block {
|
18
|
+
display: inline-block;
|
19
|
+
}
|
data/vue_crud.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'vue_crud/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "vue_crud"
|
8
|
+
spec.version = VueCrud::VERSION
|
9
|
+
spec.authors = ["Donald Chiang"]
|
10
|
+
spec.email = ["dc@mynet.com.tw"]
|
11
|
+
spec.license = "MIT"
|
12
|
+
spec.summary = %q{Vue js CRUD scaffold}
|
13
|
+
spec.description = %q{Vue js CRUD scaffold}
|
14
|
+
spec.homepage = "http://www.mynet.com.tw"
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
17
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
18
|
+
# if spec.respond_to?(:metadata)
|
19
|
+
# spec.metadata['allowed_push_host'] = 'http://mygemserver.com'
|
20
|
+
# else
|
21
|
+
# raise "RubyGems 2.0 or newer is required to protect against " \
|
22
|
+
# "public gem pushes."
|
23
|
+
# end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
f.match(%r{^(test|spec|features)/})
|
27
|
+
end
|
28
|
+
spec.bindir = "exe"
|
29
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ["lib"]
|
31
|
+
|
32
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
33
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
34
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vue_crud
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Donald Chiang
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.13'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
description: Vue js CRUD scaffold
|
56
|
+
email:
|
57
|
+
- dc@mynet.com.tw
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".travis.yml"
|
64
|
+
- Gemfile
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- bin/console
|
68
|
+
- bin/setup
|
69
|
+
- lib/generators/vue_crud/install_generator.rb
|
70
|
+
- lib/generators/vue_crud/templates/vue_crud.html
|
71
|
+
- lib/vue_crud.rb
|
72
|
+
- lib/vue_crud/engine.rb
|
73
|
+
- lib/vue_crud/version.rb
|
74
|
+
- vendor/assets/javascripts/vue_crud.js
|
75
|
+
- vendor/assets/stylesheets/vue_crud.css
|
76
|
+
- vue_crud.gemspec
|
77
|
+
homepage: http://www.mynet.com.tw
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
metadata: {}
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 2.5.1
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: Vue js CRUD scaffold
|
101
|
+
test_files: []
|