chr 0.2.4 → 0.2.5

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.
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