chr 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c1e8a41bc74d5c9663f0f5d10ac550ada054fe57
4
- data.tar.gz: cf8bb324690c3c73033768e2683cb2270fed88f3
3
+ metadata.gz: bc38de2cb0b82aa71968cd48127d83146daa2bfd
4
+ data.tar.gz: 9a373db5e5f9d4e1ce08ef020b8691b4b3c5d473
5
5
  SHA512:
6
- metadata.gz: 15adb4c217afc3aeb3c8af54f251fb7d7e358490607c2cd67643a2f67b9221001caef951abf4541d3df7621fc85eaeed4d7ec6d987c43a9e8a4a11fc99a10bdd
7
- data.tar.gz: 58ce4a8966c8c7408f57fe5f0fb9f7df2a9898e21f5c120014cb4e4bfea1f0b4d9cb5832ef249ee72f07b9ebf0ba62982e4926485f515755ec30071358da2c03
6
+ metadata.gz: b13c5bdb285b264cd540f236b6c55aa8c5671c66122798a5c1f840b8055d18c1a775d60c7fb9305c10cb3e8e06d20c616ae1fe6d296739c198418929bf1f06ca
7
+ data.tar.gz: 8223dd3c23d34d4da1cfbba9d22f7089e5ebe201a26ad6e58f6d08d482803c0613e794607f348a1e0ba7ef8dd587ce9e680bb6ce16ab1db72992f4920ea2dce3
data/README.md CHANGED
@@ -3,283 +3,53 @@
3
3
  *Powerful responsive javascript CMS for apps.*
4
4
 
5
5
 
6
- ## Rails
6
+ ## Quick Start
7
7
 
8
- An example of admin implementation setup for [Rails](https://github.com/rails/rails) app that uses [Mongoid](https://github.com/mongoid/mongoid) stack.
9
-
10
-
11
- #### Gems
12
-
13
- Add to following gems to ```Gemfile```:
14
-
15
- gem "devise"
16
- gem "mongosteen"
17
- gem "chr"
18
-
19
- This example uses ```devise``` for admins authentication.
20
-
21
-
22
- #### Admin authentication
23
-
24
- Start with running [devise](https://github.com/plataformatec/devise) generator:
25
-
26
- rails generate devise:install
27
-
28
- Setup ```Admin``` model with devise generator:
29
-
30
- rails generate devise admin
31
-
32
- Here is an example of basic ```app/models/admin.rb``` model that provides email/password authentication:
33
-
34
- ```ruby
35
- class Admin
36
- include Mongoid::Document
37
- include Mongoid::Timestamps
38
- include Mongoid::SerializableId
39
-
40
- devise :database_authenticatable,
41
- :rememberable,
42
- :authentication_keys => [ :email ]
43
-
44
- ## Database authenticatable
45
- field :email, type: String, default: ""
46
- field :encrypted_password, type: String, default: ""
47
-
48
- ## Rememberable
49
- field :remember_created_at, type: Time
50
- end
51
- ```
52
-
53
- When models are ready, setup controllers, views and configure routes.
54
-
55
- Base admin controller ```app/controllers/admin/base_controller.rb``` looks like this:
56
-
57
- ```ruby
58
- class Admin::BaseController < ActionController::Base
59
- protect_from_forgery
60
-
61
- if Rails.env.production?
62
- before_action :authenticate_admin!
63
- end
64
-
65
- def index
66
- render '/admin/index', layout: 'admin'
67
- end
68
-
69
- def bootstrap_data
70
- render json: {}
71
- end
72
- end
73
- ```
74
-
75
- Notes on code above:
76
-
77
- 1. Authentication is not required when running in development or testing environment;
78
- 2. Need to setup ```index``` view and ```admin``` layout to render admin app;
79
- 3. ```bootstrap_data``` is a placeholder for objects that might be required to be loaded when app starts.
80
-
81
- Devise would require a custom ```SessionController``` implementation in ```app/controllers/admin/devise_overrides/session_controller.rb```. ```SessionController``` sets ```admin``` layout to be used for devise views rendering and enables login by email (*looks like workaround*).
82
-
83
- ```ruby
84
- class Admin::DeviseOverrides::SessionsController < Devise::SessionsController
85
- layout 'admin'
86
-
87
- protected
88
-
89
- def configure_permitted_parameters
90
- devise_parameter_sanitizer.for(:sign_in) << :email
91
- end
92
- end
93
- ```
94
-
95
- Admin app layout ```app/views/layouts/admin.html.erb```:
96
-
97
- ```erb
98
- <!doctype html>
99
- <html>
100
- <head>
101
- <meta charset="utf-8">
102
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
103
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
104
- <meta name="apple-mobile-web-app-capable" content="yes">
105
- <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
106
- <title>Admin</title>
107
- <%= csrf_meta_tags %>
108
- <%= stylesheet_link_tag :admin, media: "all" %>
109
- <%= javascript_include_tag :admin %>
110
- </head>
111
-
112
- <%= yield %>
113
- </html>
114
- ```
115
-
116
- Admin index view ```app/views/admin/index.html.erb```:
117
-
118
- ```erb
119
- <body class='loading'>
120
- <%= link_to 'Sign Out', destroy_admin_session_path, method: :delete, style: 'display:none;' %>
121
- </body>
122
- ```
123
-
124
- New session view for devise ```app/views/admin/devise_overrides/sessions/new.html.erb```:
125
-
126
- ```erb
127
- <body class='sign-in'>
128
- <h2>Sign In</h2>
129
-
130
- <%= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
131
- <% if alert %>
132
- <p class="error"><%= alert.gsub('username', 'email').gsub('or sign up', '') %></p>
133
- <% end %>
134
-
135
- <div class="form-inputs">
136
- <%= f.input :email, required: true, autofocus: true %>
137
- <%= f.input :password, required: true %>
138
-
139
- <%= f.input :remember_me, as: :boolean if devise_mapping.rememberable? %>
140
- </div>
141
-
142
- <div class="form-actions">
143
- <%= f.button :submit, "Sign In" %>
144
- </div>
145
- <% end %>
146
- </body>
147
- ```
148
-
149
- Now connect admin and devise in ```config/routes.rb``` with:
150
-
151
- ```ruby
152
- devise_for :admins, path: "admin", controllers: { sessions: "admin/devise_overrides/sessions" }
153
- namespace :admin do
154
- get '/' => 'base#index'
155
- get '/bootstrap.json' => 'base#bootstrap_data'
156
- end
157
- ```
158
-
159
-
160
- #### Character setup
161
-
162
- Three pieces to be configured here.
163
-
164
- **First**: create ```app/assets/javascripts/admin.coffee``` with empty ```modules``` configuration:
8
+ Application setup:
165
9
 
166
10
  ```coffee
167
11
  #= require jquery
168
- #= require jquery_ujs
169
12
  #= require chr
170
13
 
14
+ postsConfig = (data) ->
15
+ itemTitleField: 'title'
16
+ arrayStore: new RestArrayStore({
17
+ resource: 'post'
18
+ path: '/admin/posts'
19
+ sortBy: 'title'
20
+ })
21
+ formSchema:
22
+ title { type: 'string' }
23
+ body: { type: 'text' }
24
+
171
25
  $ ->
172
- $.get '/admin/bootstrap.json', (response) ->
173
- config =
174
- modules: {}
26
+ config =
27
+ modules:
28
+ posts: postsConfig()
175
29
 
176
- $('body').removeClass('loading')
177
30
  chr.start(config)
178
-
179
- # append signout button to the end of sidebar menu
180
- $('a[data-method=delete]').appendTo(".sidebar .menu").show()
181
31
  ```
182
32
 
183
- **Second**: create foundation for style customization in ```app/assets/stylesheets/admin.scss```:
33
+ Styles setup:
184
34
 
185
35
  ```scss
186
- @charset "utf-8";
187
-
188
36
  @import "normalize-rails";
189
37
  @import "chr";
190
- @import "admin/signin";
191
38
  ```
192
39
 
193
- Last import in the code above is optional. But here is a default source for it as well ```app/assets/stylesheets/admin/chr/_signin.scss```:
194
-
195
- ```scss
196
- .sign-in {
197
- font-size: 14px;
198
- color: #555;
199
- margin: 3em 0 0 3em;
200
40
 
201
- h2 {
202
- text-transform: uppercase;
203
- font-size: 1em;
204
- font-size: 16px;
205
- color: $black;
206
- margin-bottom: 1.5em;
207
- }
41
+ ## Documentation
208
42
 
209
- p {
210
- margin: -1.5em 0 2em;
211
- color: $positiveColor;
212
- }
43
+ * [Start with Rails](docs/rails.md)
44
+ * [Bootstrap Data](docs/bootstrap.md)
213
45
 
214
- .form-actions, .form-inputs {
215
- max-width: 280px;
216
- }
46
+ More documentation and samples comming soon...
217
47
 
218
- .input {
219
- margin-bottom: 1.5em;
220
- }
221
48
 
222
- input.string, input.password {
223
- float: right;
224
- margin-top: -.45em;
225
- padding: .25em .5em;
226
- width: 13.5em;
227
- }
49
+ ## Character family:
228
50
 
229
- label.boolean input {
230
- margin-right: .25em;
231
- }
232
-
233
- .form-actions input {
234
- width: 100%;
235
- padding: 1em 2em;
236
- background-color: $positiveColor;
237
- border: 0;
238
- color: $white;
239
- }
240
- }
241
- ```
242
-
243
- **Third**: make sure admin assets are precompiled on production, include ```admin.js``` and ```admin.css``` in ```config/initializers/assets.rb```:
244
-
245
- ```ruby
246
- Rails.application.config.assets.precompile += %w( admin.js admin.css )
247
- ```
248
-
249
- At this point initial setup for admin app is finished and it could be accessed via: ```localhost:3000/admin```.
250
-
251
-
252
- #### Add models
253
-
254
- To be continued...
255
-
256
-
257
- #### Bootstrap data
258
-
259
- Bootstrapped data configuration example with disabled item updates and pagination:
260
-
261
- ```coffee
262
- postsConfig = (data) ->
263
- itemTitleField: 'title'
264
- disableUpdateItems: true
265
- objects: data.posts
266
- arrayStore: new MongosteenArrayStore({
267
- resource: 'post'
268
- path: '/admin/posts'
269
- sortBy: 'title'
270
- pagination: false
271
- })
272
- formSchema:
273
- title: { type: 'string' }
274
- body: { type: 'text' }
275
- ```
276
-
277
- ```disableUpdateItems``` — do not update items in the list while navigation, ```objects``` — provides initial (bootstrapped) array of objects to be added to the list, ```pagination``` — disable pagination for list. If attached as modules root list, you can access store data with: ```chr.modules.posts.arrayStore.data()```.
278
-
279
-
280
- ## Character tastes better with:
281
-
282
- - [Mongosteen](https://github.com/slate-studio/mongosteen): An easy way to add restful actions for Mongoid models
51
+ - [Character](https://github.com/slate-studio/chr): Powerful responsive javascript CMS for apps
52
+ - [Mongosteen](https://github.com/slate-studio/mongosteen): An easy way to add RESTful actions for Mongoid models
283
53
  - [Inverter](https://github.com/slate-studio/inverter): An easy way to connect Rails templates content to Character CMS
284
54
  - [Loft](https://github.com/slate-studio/loft): Media assets manager for Character CMS
285
55
 
@@ -293,4 +63,8 @@ Copyright © 2015 [Slate Studio, LLC](http://slatestudio.com). Character is free
293
63
 
294
64
  [![Slate Studio](https://slate-git-images.s3-us-west-1.amazonaws.com/slate.png)](http://slatestudio.com)
295
65
 
296
- Character is maintained and funded by [Slate Studio, LLC](http://slatestudio.com). Tweet your questions or suggestions to [@slatestudio](https://twitter.com/slatestudio) and while you’re at it follow us too.
66
+ Character is maintained and funded by [Slate Studio, LLC](http://slatestudio.com). Tweet your questions or suggestions to [@slatestudio](https://twitter.com/slatestudio) and while you’re at it follow us too.
67
+
68
+
69
+
70
+
@@ -51,7 +51,7 @@ class @Chr
51
51
 
52
52
  # if module changed, hide previous module
53
53
  if @module != @modules[crumbs[1]]
54
- @module?.hide((path == '#/')) # NOTE: animate only for root path
54
+ @module?.hide()
55
55
 
56
56
  @module = @modules[crumbs[1]] # module name on position 1
57
57
 
@@ -74,12 +74,12 @@ class @Item
74
74
 
75
75
  # show view for a arrayStore item
76
76
  if crumbs[crumbs.length - 2] == 'view'
77
- return @module.showViewByObjectId(id, @config, title, true)
77
+ return @module.showViewByObjectId(id, @config, title)
78
78
  # show objectStore item view
79
79
  if @config.objectStore
80
- return @module.showViewByObjectId('', @config, title, true)
80
+ return @module.showViewByObjectId('', @config, title)
81
81
  # show nested list
82
- @module.showNestedList(_last(crumbs), true)
82
+ @module.showNestedList(_last(crumbs))
83
83
 
84
84
 
85
85
  # PUBLIC ================================================
@@ -149,35 +149,28 @@ class @List
149
149
  @module.destroyView()
150
150
 
151
151
  if @showWithParent
152
- @hide(true)
152
+ @hide()
153
153
  else
154
- @module.hideActiveList(true)
154
+ @module.hideActiveList()
155
155
 
156
156
 
157
157
  _new: (e) ->
158
158
  chr.updateHash($(e.currentTarget).attr('href'), true)
159
- @module.showView(null, @config, 'New', true)
159
+ @module.showView(null, @config, 'New')
160
160
 
161
161
 
162
162
  # PUBLIC ================================================
163
163
 
164
- hide: (animate) ->
165
- if animate then @$el.fadeOut() else @$el.hide()
164
+ hide: ->
165
+ @$el.hide()
166
166
 
167
167
 
168
- show: (animate=false, callback) ->
169
- onShow = =>
168
+ show: (callback) ->
169
+ @$el.show 0, =>
170
170
  @$items.scrollTop(0)
171
171
  @config.onListShow?(@)
172
172
  callback?()
173
173
 
174
- if animate
175
- # z-index workaround to remove blink effect
176
- @$el.css({ 'z-index': 1, 'box-shadow': 'none' })
177
- @$el.fadeIn $.fx.speeds._default, => @$el.css({ 'z-index': '', 'box-shadow': '' }) ; onShow()
178
- else
179
- @$el.show() ; onShow()
180
-
181
174
 
182
175
  updateItems: ->
183
176
  if not @config.disableUpdateItems
@@ -16,16 +16,16 @@
16
16
  #
17
17
  # Public methods:
18
18
  # addNestedList (listName, config, parentList)
19
- # showNestedList (listName, animate=false)
19
+ # showNestedList (listName)
20
20
  # hideNestedLists (exceptList)
21
21
  # visibleNestedListShownWithParent ()
22
- # showRootList()
23
- # hideActiveList (animate=false)
24
- # showView (object, config, title, animate=false)
25
- # showViewByObjectId (objectId, config, title, animate=false)
22
+ # showRootList ()
23
+ # hideActiveList ()
24
+ # showView (object, config, title)
25
+ # showViewByObjectId (objectId, config, title)
26
26
  # destroyView ()
27
27
  # show ()
28
- # hide (animate=false)
28
+ # hide ()
29
29
  #
30
30
  # -----------------------------------------------------------------------------
31
31
  class @Module
@@ -77,22 +77,21 @@ class @Module
77
77
  @nestedLists[listName] = new List(this, listName, config, parentList)
78
78
 
79
79
 
80
- # shows one of nested lists, with or without animation
81
- showNestedList: (listName, animate=false) ->
80
+ # shows one of nested lists
81
+ showNestedList: (listName) ->
82
82
  listToShow = @nestedLists[listName]
83
83
 
84
84
  if listToShow.showWithParent
85
85
  # list works as view, never becomes active
86
86
  listToShow.updateItems()
87
- listToShow.show animate, => @hideNestedLists(exceptList=listName)
87
+ listToShow.show => @hideNestedLists(exceptList=listName)
88
88
 
89
89
  else
90
90
  @activeList = listToShow
91
91
  @_update_active_list_items()
92
- @activeList.show(animate)
92
+ @activeList.show()
93
93
 
94
- # hide view
95
- if animate and @view then @view.$el.fadeOut $.fx.speeds._default, => @destroyView()
94
+ @destroyView()
96
95
 
97
96
 
98
97
  hideNestedLists: (exceptList) ->
@@ -105,28 +104,28 @@ class @Module
105
104
  if list.isVisible() && list.showWithParent then return list
106
105
 
107
106
 
108
- showRootList: () ->
107
+ showRootList: ->
109
108
  @destroyView()
110
109
  while @activeList != @rootList
111
- @hideActiveList(false)
110
+ @hideActiveList()
112
111
 
113
112
 
114
- hideActiveList: (animate=false)->
115
- if animate then @activeList.$el.fadeOut() else @activeList.$el.hide()
113
+ hideActiveList: ->
114
+ @activeList.$el.hide()
116
115
  @activeList = @activeList.parentList
117
116
 
118
117
 
119
- showView: (object, config, title, animate=false) ->
118
+ showView: (object, config, title) ->
120
119
  newView = new View(this, config, @_view_path(), object, title)
121
120
  @chr.$el.append(newView.$el)
122
121
 
123
- newView.show animate, =>
122
+ newView.show =>
124
123
  @destroyView()
125
124
  @view = newView
126
125
 
127
126
 
128
- showViewByObjectId: (objectId, config, title, animate=false) ->
129
- onSuccess = (object) => @showView(object, config, title, animate)
127
+ showViewByObjectId: (objectId, config, title) ->
128
+ onSuccess = (object) => @showView(object, config, title)
130
129
  onError = -> chr.showError("can\'t show view for requested object")
131
130
 
132
131
  if objectId == ''
@@ -142,19 +141,13 @@ class @Module
142
141
  show: ->
143
142
  @_update_active_list_items()
144
143
  @$el.show()
145
- @activeList.show(false)
144
+ @activeList.show()
146
145
 
147
146
 
148
- hide: (animate=false) ->
147
+ hide: ->
149
148
  @hideNestedLists()
150
-
151
- if animate
152
- # TODO: move animation to the view class
153
- if @view then @view.$el.fadeOut $.fx.speeds._default, => @destroyView()
154
- @$el.fadeOut()
155
- else
156
- @destroyView()
157
- @$el.hide()
149
+ @destroyView()
150
+ @$el.hide()
158
151
 
159
152
 
160
153
 
@@ -16,11 +16,9 @@
16
16
  # disableSave - do not add save button in header
17
17
  # fullsizeView — use fullsize layout in desktop mode
18
18
  # onViewShow - on show callback
19
- # onShowAnimation - animation method to be used on show
20
- # onCloseAnimation - animation method to be used on close
21
19
  #
22
20
  # public methods:
23
- # show(animate, callback)
21
+ # show(callback)
24
22
  # destroy()
25
23
  #
26
24
  # -----------------------------------------------------------------------------
@@ -34,12 +32,6 @@ class @View
34
32
  if @config.fullsizeView
35
33
  @$el.addClass 'fullsize'
36
34
 
37
- # animations
38
- @onShowAnimation = @config.onShowAnimation
39
- @onCloseAnimation = @config.onCloseAnimation
40
- @onShowAnimation ?= (callback) => @$el.fadeIn $.fx.speeds._default, -> callback()
41
- @onCloseAnimation ?= (callback) => @$el.fadeOut $.fx.speeds._default, -> callback()
42
-
43
35
  # header
44
36
  @$header =$ "<header></header>"
45
37
  @$title =$ "<div class='title'></div>"
@@ -96,7 +88,7 @@ class @View
96
88
  # EVENTS ================================================
97
89
 
98
90
  _close: (e) ->
99
- @onCloseAnimation(=> @destroy())
91
+ @destroy()
100
92
 
101
93
 
102
94
  _save: (e) ->
@@ -122,23 +114,18 @@ class @View
122
114
  e.preventDefault()
123
115
  if confirm("Are you sure?")
124
116
  @store.remove @object._id,
125
- onSuccess: => @onCloseAnimation( => chr.updateHash("#/#{ @closePath }", true) ; @destroy() )
117
+ onSuccess: => chr.updateHash("#/#{ @closePath }", true) ; @destroy()
126
118
  onError: -> chr.showError('Can\'t delete object.')
127
119
 
128
120
 
129
121
  # PUBLIC ================================================
130
122
 
131
- show: (animate, callback) ->
132
- after_show = =>
123
+ show: (callback) ->
124
+ @$el.show 0, =>
133
125
  callback?()
134
126
  @form.initializePlugins()
135
127
  @config.onViewShow?(@)
136
128
 
137
- if animate
138
- @onShowAnimation(=> after_show())
139
- else
140
- @$el.show(0, => after_show())
141
-
142
129
 
143
130
  destroy: ->
144
131
  @form.destroy()
@@ -31,19 +31,16 @@
31
31
  isFocused: false,
32
32
 
33
33
  checkOffset: function () {
34
- if ( !this.redactor.fullscreen.isOpen )
35
- {
36
- var boxOffset = this.redactor.$box.offset();
37
-
38
- var isBelowBoxTop = boxOffset.top - this.viewHeaderHeight <= 0;
39
- //var isAboveBoxBottom = boxOffset.top + this.redactor.$box.outerHeight() - this.redactor.$toolbar.outerHeight() - this.$window.scrollTop() >= 0;
40
- var isAboveBoxBottom = this.redactor.$box.outerHeight() + boxOffset.top - this.viewHeaderHeight - this.redactor.$toolbar.outerHeight() >= 0;
41
-
42
- if (isBelowBoxTop && isAboveBoxBottom) {
43
- this.fix();
44
- } else {
45
- this.unfix();
46
- }
34
+ var boxOffset = this.redactor.$box.offset();
35
+
36
+ var isBelowBoxTop = boxOffset.top - this.viewHeaderHeight <= 0;
37
+ //var isAboveBoxBottom = boxOffset.top + this.redactor.$box.outerHeight() - this.redactor.$toolbar.outerHeight() - this.$window.scrollTop() >= 0;
38
+ var isAboveBoxBottom = this.redactor.$box.outerHeight() + boxOffset.top - this.viewHeaderHeight - this.redactor.$toolbar.outerHeight() >= 0;
39
+
40
+ if (isBelowBoxTop && isAboveBoxBottom) {
41
+ this.fix();
42
+ } else {
43
+ this.unfix();
47
44
  }
48
45
  },
49
46
 
@@ -29,21 +29,16 @@ class @InputRedactor extends InputString
29
29
  # PUBLIC ================================================
30
30
 
31
31
  initialize: ->
32
+ plugins = [ 'fixedtoolbar' ]
33
+ if Loft then plugins.push('loft')
34
+
32
35
  redactor_options =
33
36
  focus: false
34
37
  imageFloatMargin: '20px'
35
38
  buttonSource: true
36
39
  pastePlainText: true
37
- plugins: [ 'fixedtoolbar', 'loft' ]
38
- buttons: [ 'html',
39
- 'formatting',
40
- 'bold',
41
- 'italic',
42
- 'deleted',
43
- 'alignment',
44
- 'unorderedlist',
45
- 'orderedlist',
46
- 'link' ]
40
+ plugins: plugins
41
+ buttons: [ 'html', 'formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist', 'link' ]
47
42
 
48
43
  @config.redactorOptions ?= {}
49
44
  $.extend(redactor_options, @config.redactorOptions)
data/bower.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chr",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "homepage": "https://github.com/slate-studio/chr",
5
5
  "authors": [
6
6
  "Slate Studio (http://www.slatestudio.com)"
data/dist/chr.js CHANGED
@@ -2762,7 +2762,7 @@ this.Chr = (function() {
2762
2762
  crumbs = path.split('/');
2763
2763
  if (this.module !== this.modules[crumbs[1]]) {
2764
2764
  if ((ref = this.module) != null) {
2765
- ref.hide(path === '#/');
2765
+ ref.hide();
2766
2766
  }
2767
2767
  }
2768
2768
  this.module = this.modules[crumbs[1]];
@@ -2912,15 +2912,12 @@ this.Module = (function() {
2912
2912
  return this.nestedLists[listName] = new List(this, listName, config, parentList);
2913
2913
  };
2914
2914
 
2915
- Module.prototype.showNestedList = function(listName, animate) {
2915
+ Module.prototype.showNestedList = function(listName) {
2916
2916
  var listToShow;
2917
- if (animate == null) {
2918
- animate = false;
2919
- }
2920
2917
  listToShow = this.nestedLists[listName];
2921
2918
  if (listToShow.showWithParent) {
2922
2919
  listToShow.updateItems();
2923
- listToShow.show(animate, (function(_this) {
2920
+ listToShow.show((function(_this) {
2924
2921
  return function() {
2925
2922
  var exceptList;
2926
2923
  return _this.hideNestedLists(exceptList = listName);
@@ -2929,15 +2926,9 @@ this.Module = (function() {
2929
2926
  } else {
2930
2927
  this.activeList = listToShow;
2931
2928
  this._update_active_list_items();
2932
- this.activeList.show(animate);
2933
- }
2934
- if (animate && this.view) {
2935
- return this.view.$el.fadeOut($.fx.speeds._default, (function(_this) {
2936
- return function() {
2937
- return _this.destroyView();
2938
- };
2939
- })(this));
2929
+ this.activeList.show();
2940
2930
  }
2931
+ return this.destroyView();
2941
2932
  };
2942
2933
 
2943
2934
  Module.prototype.hideNestedLists = function(exceptList) {
@@ -2970,31 +2961,21 @@ this.Module = (function() {
2970
2961
  this.destroyView();
2971
2962
  results = [];
2972
2963
  while (this.activeList !== this.rootList) {
2973
- results.push(this.hideActiveList(false));
2964
+ results.push(this.hideActiveList());
2974
2965
  }
2975
2966
  return results;
2976
2967
  };
2977
2968
 
2978
- Module.prototype.hideActiveList = function(animate) {
2979
- if (animate == null) {
2980
- animate = false;
2981
- }
2982
- if (animate) {
2983
- this.activeList.$el.fadeOut();
2984
- } else {
2985
- this.activeList.$el.hide();
2986
- }
2969
+ Module.prototype.hideActiveList = function() {
2970
+ this.activeList.$el.hide();
2987
2971
  return this.activeList = this.activeList.parentList;
2988
2972
  };
2989
2973
 
2990
- Module.prototype.showView = function(object, config, title, animate) {
2974
+ Module.prototype.showView = function(object, config, title) {
2991
2975
  var newView;
2992
- if (animate == null) {
2993
- animate = false;
2994
- }
2995
2976
  newView = new View(this, config, this._view_path(), object, title);
2996
2977
  this.chr.$el.append(newView.$el);
2997
- return newView.show(animate, (function(_this) {
2978
+ return newView.show((function(_this) {
2998
2979
  return function() {
2999
2980
  _this.destroyView();
3000
2981
  return _this.view = newView;
@@ -3002,14 +2983,11 @@ this.Module = (function() {
3002
2983
  })(this));
3003
2984
  };
3004
2985
 
3005
- Module.prototype.showViewByObjectId = function(objectId, config, title, animate) {
2986
+ Module.prototype.showViewByObjectId = function(objectId, config, title) {
3006
2987
  var onError, onSuccess;
3007
- if (animate == null) {
3008
- animate = false;
3009
- }
3010
2988
  onSuccess = (function(_this) {
3011
2989
  return function(object) {
3012
- return _this.showView(object, config, title, animate);
2990
+ return _this.showView(object, config, title);
3013
2991
  };
3014
2992
  })(this);
3015
2993
  onError = function() {
@@ -3036,27 +3014,13 @@ this.Module = (function() {
3036
3014
  Module.prototype.show = function() {
3037
3015
  this._update_active_list_items();
3038
3016
  this.$el.show();
3039
- return this.activeList.show(false);
3017
+ return this.activeList.show();
3040
3018
  };
3041
3019
 
3042
- Module.prototype.hide = function(animate) {
3043
- if (animate == null) {
3044
- animate = false;
3045
- }
3020
+ Module.prototype.hide = function() {
3046
3021
  this.hideNestedLists();
3047
- if (animate) {
3048
- if (this.view) {
3049
- this.view.$el.fadeOut($.fx.speeds._default, (function(_this) {
3050
- return function() {
3051
- return _this.destroyView();
3052
- };
3053
- })(this));
3054
- }
3055
- return this.$el.fadeOut();
3056
- } else {
3057
- this.destroyView();
3058
- return this.$el.hide();
3059
- }
3022
+ this.destroyView();
3023
+ return this.$el.hide();
3060
3024
  };
3061
3025
 
3062
3026
  return Module;
@@ -3392,31 +3356,23 @@ this.List = (function() {
3392
3356
  this.module.chr.unsetActiveListItems();
3393
3357
  this.module.destroyView();
3394
3358
  if (this.showWithParent) {
3395
- return this.hide(true);
3359
+ return this.hide();
3396
3360
  } else {
3397
- return this.module.hideActiveList(true);
3361
+ return this.module.hideActiveList();
3398
3362
  }
3399
3363
  };
3400
3364
 
3401
3365
  List.prototype._new = function(e) {
3402
3366
  chr.updateHash($(e.currentTarget).attr('href'), true);
3403
- return this.module.showView(null, this.config, 'New', true);
3367
+ return this.module.showView(null, this.config, 'New');
3404
3368
  };
3405
3369
 
3406
- List.prototype.hide = function(animate) {
3407
- if (animate) {
3408
- return this.$el.fadeOut();
3409
- } else {
3410
- return this.$el.hide();
3411
- }
3370
+ List.prototype.hide = function() {
3371
+ return this.$el.hide();
3412
3372
  };
3413
3373
 
3414
- List.prototype.show = function(animate, callback) {
3415
- var onShow;
3416
- if (animate == null) {
3417
- animate = false;
3418
- }
3419
- onShow = (function(_this) {
3374
+ List.prototype.show = function(callback) {
3375
+ return this.$el.show(0, (function(_this) {
3420
3376
  return function() {
3421
3377
  var base;
3422
3378
  _this.$items.scrollTop(0);
@@ -3425,25 +3381,7 @@ this.List = (function() {
3425
3381
  }
3426
3382
  return typeof callback === "function" ? callback() : void 0;
3427
3383
  };
3428
- })(this);
3429
- if (animate) {
3430
- this.$el.css({
3431
- 'z-index': 1,
3432
- 'box-shadow': 'none'
3433
- });
3434
- return this.$el.fadeIn($.fx.speeds._default, (function(_this) {
3435
- return function() {
3436
- _this.$el.css({
3437
- 'z-index': '',
3438
- 'box-shadow': ''
3439
- });
3440
- return onShow();
3441
- };
3442
- })(this));
3443
- } else {
3444
- this.$el.show();
3445
- return onShow();
3446
- }
3384
+ })(this));
3447
3385
  };
3448
3386
 
3449
3387
  List.prototype.updateItems = function() {
@@ -3548,12 +3486,12 @@ this.Item = (function() {
3548
3486
  id = $(e.currentTarget).attr('data-id');
3549
3487
  chr.updateHash(hash, true);
3550
3488
  if (crumbs[crumbs.length - 2] === 'view') {
3551
- return this.module.showViewByObjectId(id, this.config, title, true);
3489
+ return this.module.showViewByObjectId(id, this.config, title);
3552
3490
  }
3553
3491
  if (this.config.objectStore) {
3554
- return this.module.showViewByObjectId('', this.config, title, true);
3492
+ return this.module.showViewByObjectId('', this.config, title);
3555
3493
  }
3556
- return this.module.showNestedList(_last(crumbs), true);
3494
+ return this.module.showNestedList(_last(crumbs));
3557
3495
  };
3558
3496
 
3559
3497
  Item.prototype.render = function() {
@@ -3599,26 +3537,6 @@ this.View = (function() {
3599
3537
  if (this.config.fullsizeView) {
3600
3538
  this.$el.addClass('fullsize');
3601
3539
  }
3602
- this.onShowAnimation = this.config.onShowAnimation;
3603
- this.onCloseAnimation = this.config.onCloseAnimation;
3604
- if (this.onShowAnimation == null) {
3605
- this.onShowAnimation = (function(_this) {
3606
- return function(callback) {
3607
- return _this.$el.fadeIn($.fx.speeds._default, function() {
3608
- return callback();
3609
- });
3610
- };
3611
- })(this);
3612
- }
3613
- if (this.onCloseAnimation == null) {
3614
- this.onCloseAnimation = (function(_this) {
3615
- return function(callback) {
3616
- return _this.$el.fadeOut($.fx.speeds._default, function() {
3617
- return callback();
3618
- });
3619
- };
3620
- })(this);
3621
- }
3622
3540
  this.$header = $("<header></header>");
3623
3541
  this.$title = $("<div class='title'></div>");
3624
3542
  this.$header.append(this.$title);
@@ -3690,11 +3608,7 @@ this.View = (function() {
3690
3608
  };
3691
3609
 
3692
3610
  View.prototype._close = function(e) {
3693
- return this.onCloseAnimation((function(_this) {
3694
- return function() {
3695
- return _this.destroy();
3696
- };
3697
- })(this));
3611
+ return this.destroy();
3698
3612
  };
3699
3613
 
3700
3614
  View.prototype._save = function(e) {
@@ -3741,10 +3655,8 @@ this.View = (function() {
3741
3655
  return this.store.remove(this.object._id, {
3742
3656
  onSuccess: (function(_this) {
3743
3657
  return function() {
3744
- return _this.onCloseAnimation(function() {
3745
- chr.updateHash("#/" + _this.closePath, true);
3746
- return _this.destroy();
3747
- });
3658
+ chr.updateHash("#/" + _this.closePath, true);
3659
+ return _this.destroy();
3748
3660
  };
3749
3661
  })(this),
3750
3662
  onError: function() {
@@ -3754,9 +3666,8 @@ this.View = (function() {
3754
3666
  }
3755
3667
  };
3756
3668
 
3757
- View.prototype.show = function(animate, callback) {
3758
- var after_show;
3759
- after_show = (function(_this) {
3669
+ View.prototype.show = function(callback) {
3670
+ return this.$el.show(0, (function(_this) {
3760
3671
  return function() {
3761
3672
  var base;
3762
3673
  if (typeof callback === "function") {
@@ -3765,20 +3676,7 @@ this.View = (function() {
3765
3676
  _this.form.initializePlugins();
3766
3677
  return typeof (base = _this.config).onViewShow === "function" ? base.onViewShow(_this) : void 0;
3767
3678
  };
3768
- })(this);
3769
- if (animate) {
3770
- return this.onShowAnimation((function(_this) {
3771
- return function() {
3772
- return after_show();
3773
- };
3774
- })(this));
3775
- } else {
3776
- return this.$el.show(0, (function(_this) {
3777
- return function() {
3778
- return after_show();
3779
- };
3780
- })(this));
3781
- }
3679
+ })(this));
3782
3680
  };
3783
3681
 
3784
3682
  View.prototype.destroy = function() {
@@ -31,19 +31,16 @@
31
31
  isFocused: false,
32
32
 
33
33
  checkOffset: function () {
34
- if ( !this.redactor.fullscreen.isOpen )
35
- {
36
- var boxOffset = this.redactor.$box.offset();
37
-
38
- var isBelowBoxTop = boxOffset.top - this.viewHeaderHeight <= 0;
39
- //var isAboveBoxBottom = boxOffset.top + this.redactor.$box.outerHeight() - this.redactor.$toolbar.outerHeight() - this.$window.scrollTop() >= 0;
40
- var isAboveBoxBottom = this.redactor.$box.outerHeight() + boxOffset.top - this.viewHeaderHeight - this.redactor.$toolbar.outerHeight() >= 0;
41
-
42
- if (isBelowBoxTop && isAboveBoxBottom) {
43
- this.fix();
44
- } else {
45
- this.unfix();
46
- }
34
+ var boxOffset = this.redactor.$box.offset();
35
+
36
+ var isBelowBoxTop = boxOffset.top - this.viewHeaderHeight <= 0;
37
+ //var isAboveBoxBottom = boxOffset.top + this.redactor.$box.outerHeight() - this.redactor.$toolbar.outerHeight() - this.$window.scrollTop() >= 0;
38
+ var isAboveBoxBottom = this.redactor.$box.outerHeight() + boxOffset.top - this.viewHeaderHeight - this.redactor.$toolbar.outerHeight() >= 0;
39
+
40
+ if (isBelowBoxTop && isAboveBoxBottom) {
41
+ this.fix();
42
+ } else {
43
+ this.unfix();
47
44
  }
48
45
  },
49
46
 
@@ -126,14 +123,18 @@ this.InputRedactor = (function(superClass) {
126
123
  };
127
124
 
128
125
  InputRedactor.prototype.initialize = function() {
129
- var base, base1, redactor_options;
126
+ var base, base1, plugins, redactor_options;
127
+ plugins = ['fixedtoolbar'];
128
+ if (Loft) {
129
+ plugins.push('loft');
130
+ }
130
131
  redactor_options = {
131
132
  focus: false,
132
133
  imageFloatMargin: '20px',
133
134
  buttonSource: true,
134
135
  pastePlainText: true,
135
- plugins: ['fixedtoolbar', 'loft'],
136
- buttons: ['html', 'formatting', 'bold', 'italic', 'deleted', 'alignment', 'unorderedlist', 'orderedlist', 'link']
136
+ plugins: plugins,
137
+ buttons: ['html', 'formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist', 'link']
137
138
  };
138
139
  if ((base = this.config).redactorOptions == null) {
139
140
  base.redactorOptions = {};
data/docs/bootstrap.md ADDED
@@ -0,0 +1,23 @@
1
+ # Character
2
+
3
+ ## Bootstrap Data
4
+
5
+ Bootstrapped data configuration example with disabled item updates and pagination:
6
+
7
+ ```coffee
8
+ postsConfig = (data) ->
9
+ itemTitleField: 'title'
10
+ disableUpdateItems: true
11
+ objects: data.posts
12
+ arrayStore: new MongosteenArrayStore({
13
+ resource: 'post'
14
+ path: '/admin/posts'
15
+ sortBy: 'title'
16
+ pagination: false
17
+ })
18
+ formSchema:
19
+ title: { type: 'string' }
20
+ body: { type: 'text' }
21
+ ```
22
+
23
+ ```disableUpdateItems``` — do not update items in the list while navigation, ```objects``` — provides initial (bootstrapped) array of objects to be added to the list, ```pagination``` — disable pagination for list. If attached as modules root list, you can access store data with: ```chr.modules.posts.arrayStore.data()```.
data/docs/rails.md ADDED
@@ -0,0 +1,255 @@
1
+ # Character
2
+
3
+ ## Rails
4
+
5
+ An example of admin implementation setup for [Rails](https://github.com/rails/rails) app that uses [Mongoid](https://github.com/mongoid/mongoid) stack.
6
+
7
+
8
+ ### Gems
9
+
10
+ Add to following gems to ```Gemfile```:
11
+
12
+ gem "devise"
13
+ gem "mongosteen"
14
+ gem "chr"
15
+
16
+ This example uses ```devise``` for admins authentication.
17
+
18
+
19
+ ### Admin authentication
20
+
21
+ Start with running [devise](https://github.com/plataformatec/devise) generator:
22
+
23
+ rails generate devise:install
24
+
25
+ Setup ```Admin``` model with devise generator:
26
+
27
+ rails generate devise admin
28
+
29
+ Here is an example of basic ```app/models/admin.rb``` model that provides email/password authentication:
30
+
31
+ ```ruby
32
+ class Admin
33
+ include Mongoid::Document
34
+ include Mongoid::Timestamps
35
+ include Mongoid::SerializableId
36
+
37
+ devise :database_authenticatable,
38
+ :rememberable,
39
+ :authentication_keys => [ :email ]
40
+
41
+ ## Database authenticatable
42
+ field :email, type: String, default: ""
43
+ field :encrypted_password, type: String, default: ""
44
+
45
+ ## Rememberable
46
+ field :remember_created_at, type: Time
47
+ end
48
+ ```
49
+
50
+ When models are ready, setup controllers, views and configure routes.
51
+
52
+ Base admin controller ```app/controllers/admin/base_controller.rb``` looks like this:
53
+
54
+ ```ruby
55
+ class Admin::BaseController < ActionController::Base
56
+ protect_from_forgery
57
+
58
+ if Rails.env.production?
59
+ before_action :authenticate_admin!
60
+ end
61
+
62
+ def index
63
+ render '/admin/index', layout: 'admin'
64
+ end
65
+
66
+ def bootstrap_data
67
+ render json: {}
68
+ end
69
+ end
70
+ ```
71
+
72
+ Notes on code above:
73
+
74
+ 1. Authentication is not required when running in development or testing environment;
75
+ 2. Need to setup ```index``` view and ```admin``` layout to render admin app;
76
+ 3. ```bootstrap_data``` is a placeholder for objects that might be required to be loaded when app starts.
77
+
78
+ Devise would require a custom ```SessionController``` implementation in ```app/controllers/admin/devise_overrides/session_controller.rb```. ```SessionController``` sets ```admin``` layout to be used for devise views rendering and enables login by email (*looks like workaround*).
79
+
80
+ ```ruby
81
+ class Admin::DeviseOverrides::SessionsController < Devise::SessionsController
82
+ layout 'admin'
83
+
84
+ protected
85
+
86
+ def configure_permitted_parameters
87
+ devise_parameter_sanitizer.for(:sign_in) << :email
88
+ end
89
+ end
90
+ ```
91
+
92
+ Admin app layout ```app/views/layouts/admin.html.erb```:
93
+
94
+ ```erb
95
+ <!doctype html>
96
+ <html>
97
+ <head>
98
+ <meta charset="utf-8">
99
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
100
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
101
+ <meta name="apple-mobile-web-app-capable" content="yes">
102
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
103
+ <title>Admin</title>
104
+ <%= csrf_meta_tags %>
105
+ <%= stylesheet_link_tag :admin, media: "all" %>
106
+ <%= javascript_include_tag :admin %>
107
+ </head>
108
+
109
+ <%= yield %>
110
+ </html>
111
+ ```
112
+
113
+ Admin index view ```app/views/admin/index.html.erb```:
114
+
115
+ ```erb
116
+ <body class='loading'>
117
+ <%= link_to 'Sign Out', destroy_admin_session_path, method: :delete, style: 'display:none;' %>
118
+ </body>
119
+ ```
120
+
121
+ New session view for devise ```app/views/admin/devise_overrides/sessions/new.html.erb```:
122
+
123
+ ```erb
124
+ <body class='sign-in'>
125
+ <h2>Sign In</h2>
126
+
127
+ <%= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
128
+ <% if alert %>
129
+ <p class="error"><%= alert.gsub('username', 'email').gsub('or sign up', '') %></p>
130
+ <% end %>
131
+
132
+ <div class="form-inputs">
133
+ <%= f.input :email, required: true, autofocus: true %>
134
+ <%= f.input :password, required: true %>
135
+
136
+ <%= f.input :remember_me, as: :boolean if devise_mapping.rememberable? %>
137
+ </div>
138
+
139
+ <div class="form-actions">
140
+ <%= f.button :submit, "Sign In" %>
141
+ </div>
142
+ <% end %>
143
+ </body>
144
+ ```
145
+
146
+ Now connect admin and devise in ```config/routes.rb``` with:
147
+
148
+ ```ruby
149
+ devise_for :admins, path: "admin", controllers: { sessions: "admin/devise_overrides/sessions" }
150
+ namespace :admin do
151
+ get '/' => 'base#index'
152
+ get '/bootstrap.json' => 'base#bootstrap_data'
153
+ end
154
+ ```
155
+
156
+
157
+ ### Character setup
158
+
159
+ Three pieces to be configured here.
160
+
161
+ **First**: create ```app/assets/javascripts/admin.coffee``` with empty ```modules``` configuration:
162
+
163
+ ```coffee
164
+ #= require jquery
165
+ #= require jquery_ujs
166
+ #= require chr
167
+
168
+ $ ->
169
+ $.get '/admin/bootstrap.json', (response) ->
170
+ config =
171
+ modules: {}
172
+
173
+ $('body').removeClass('loading')
174
+ chr.start(config)
175
+
176
+ # append signout button to the end of sidebar menu
177
+ $('a[data-method=delete]').appendTo(".sidebar .menu").show()
178
+ ```
179
+
180
+ **Second**: create foundation for style customization in ```app/assets/stylesheets/admin.scss```:
181
+
182
+ ```scss
183
+ @charset "utf-8";
184
+
185
+ @import "normalize-rails";
186
+ @import "chr";
187
+ @import "admin/signin";
188
+ ```
189
+
190
+ Last import in the code above is optional. But here is a default source for it as well ```app/assets/stylesheets/admin/chr/_signin.scss```:
191
+
192
+ ```scss
193
+ .sign-in {
194
+ font-size: 14px;
195
+ color: #555;
196
+ margin: 3em 0 0 3em;
197
+
198
+ h2 {
199
+ text-transform: uppercase;
200
+ font-size: 1em;
201
+ font-size: 16px;
202
+ color: $black;
203
+ margin-bottom: 1.5em;
204
+ }
205
+
206
+ p {
207
+ margin: -1.5em 0 2em;
208
+ color: $positiveColor;
209
+ }
210
+
211
+ .form-actions, .form-inputs {
212
+ max-width: 280px;
213
+ }
214
+
215
+ .input {
216
+ margin-bottom: 1.5em;
217
+ }
218
+
219
+ input.string, input.password {
220
+ float: right;
221
+ margin-top: -.45em;
222
+ padding: .25em .5em;
223
+ width: 13.5em;
224
+ }
225
+
226
+ label.boolean input {
227
+ margin-right: .25em;
228
+ }
229
+
230
+ .form-actions input {
231
+ width: 100%;
232
+ padding: 1em 2em;
233
+ background-color: $positiveColor;
234
+ border: 0;
235
+ color: $white;
236
+ }
237
+ }
238
+ ```
239
+
240
+ **Third**: make sure admin assets are precompiled on production, include ```admin.js``` and ```admin.css``` in ```config/initializers/assets.rb```:
241
+
242
+ ```ruby
243
+ Rails.application.config.assets.precompile += %w( admin.js admin.css )
244
+ ```
245
+
246
+ At this point initial setup for admin app is finished and it could be accessed via: ```localhost:3000/admin```.
247
+
248
+
249
+ ### Add models
250
+
251
+ To be continued...
252
+
253
+
254
+
255
+
data/lib/chr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Chr
2
- VERSION = "0.2.4"
2
+ VERSION = "0.2.5"
3
3
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chr",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "devDependencies": {
5
5
  "grunt": "^0.4.5",
6
6
  "grunt-contrib-clean": "^0.6.0",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Kravets
@@ -133,11 +133,12 @@ files:
133
133
  - dist/input-redactor.js
134
134
  - docs/assets.md
135
135
  - docs/basics.md
136
- - docs/bootstrap-data.md
136
+ - docs/bootstrap.md
137
137
  - docs/custom-inputs.md
138
138
  - docs/form.md
139
139
  - docs/internals.md
140
140
  - docs/nested-forms.md
141
+ - docs/rails.md
141
142
  - docs/redactor-js.md
142
143
  - docs/scopes.md
143
144
  - lib/chr.rb
File without changes