chr 0.1.0

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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/CONTRIBUTING.md +24 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE.md +21 -0
  6. data/README.md +18 -0
  7. data/Rakefile +3 -0
  8. data/app/assets/javascripts/chr.coffee +41 -0
  9. data/app/assets/javascripts/chr/core/_chr.coffee +89 -0
  10. data/app/assets/javascripts/chr/core/_item.coffee +85 -0
  11. data/app/assets/javascripts/chr/core/_list.coffee +154 -0
  12. data/app/assets/javascripts/chr/core/_listReorder.coffee +70 -0
  13. data/app/assets/javascripts/chr/core/_listScroll.coffee +23 -0
  14. data/app/assets/javascripts/chr/core/_listSearch.coffee +28 -0
  15. data/app/assets/javascripts/chr/core/_module.coffee +98 -0
  16. data/app/assets/javascripts/chr/core/_utils.coffee +50 -0
  17. data/app/assets/javascripts/chr/core/_view.coffee +121 -0
  18. data/app/assets/javascripts/chr/form/_form.coffee +205 -0
  19. data/app/assets/javascripts/chr/form/_inputCheckbox.coffee +70 -0
  20. data/app/assets/javascripts/chr/form/_inputColor.coffee +35 -0
  21. data/app/assets/javascripts/chr/form/_inputFile.coffee +82 -0
  22. data/app/assets/javascripts/chr/form/_inputHidden.coffee +41 -0
  23. data/app/assets/javascripts/chr/form/_inputList.coffee +142 -0
  24. data/app/assets/javascripts/chr/form/_inputSelect.coffee +59 -0
  25. data/app/assets/javascripts/chr/form/_inputString.coffee +87 -0
  26. data/app/assets/javascripts/chr/form/_inputText.coffee +23 -0
  27. data/app/assets/javascripts/chr/form/_nestedForm.coffee +164 -0
  28. data/app/assets/javascripts/chr/store/_store.coffee +104 -0
  29. data/app/assets/javascripts/chr/store/_storeRails.coffee +167 -0
  30. data/app/assets/javascripts/chr/vendor/jquery.scrollparent.js +14 -0
  31. data/app/assets/javascripts/chr/vendor/jquery.textarea_autosize.js +55 -0
  32. data/app/assets/javascripts/chr/vendor/jquery.typeahead.js +1782 -0
  33. data/app/assets/javascripts/chr/vendor/slip.js +804 -0
  34. data/app/assets/stylesheets/_chr.scss +7 -0
  35. data/app/assets/stylesheets/core/_icons.scss +124 -0
  36. data/app/assets/stylesheets/core/_list.scss +44 -0
  37. data/app/assets/stylesheets/core/_main.scss +89 -0
  38. data/app/assets/stylesheets/core/_responsive.scss +41 -0
  39. data/app/assets/stylesheets/form/_form.scss +50 -0
  40. data/app/assets/stylesheets/form/_input_checkbox.scss +87 -0
  41. data/app/assets/stylesheets/form/_input_color.scss +10 -0
  42. data/app/assets/stylesheets/form/_input_file.scss +28 -0
  43. data/app/assets/stylesheets/form/_input_list.scss +36 -0
  44. data/app/assets/stylesheets/form/_input_string.scss +8 -0
  45. data/app/assets/stylesheets/form/_input_text.scss +48 -0
  46. data/app/assets/stylesheets/form/_nested_form.scss +26 -0
  47. data/chr.gemspec +34 -0
  48. data/lib/chr.rb +15 -0
  49. data/lib/chr/engine.rb +5 -0
  50. data/lib/chr/version.rb +3 -0
  51. metadata +152 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 041ba212dd13b510084036d1bc6612a42cb48d78
4
+ data.tar.gz: f61e509e945fde982a8ed4c98bd859a09e7e989e
5
+ SHA512:
6
+ metadata.gz: 6acf539bd041adb2c19902af2244caa8f7994726e98c0006fa1f9834daa0ce5cc2f2b8d53ceff1d50c6c0d2cccc1d13e06c9c1adb09d4af79a29f6b195b1aa9f
7
+ data.tar.gz: 7ce77b17204fcb13bd95c76a5abd26afe740a082dc6d1bee0f9db760cdc2421ccd2d70a883123b224138917163031232bdebfafbeb2e56ffceaf695ed55c4e7d
@@ -0,0 +1,7 @@
1
+ _site
2
+ .DS_store
3
+ .sass-cache
4
+ *gem
5
+ *swp
6
+ Gemfile.lock
7
+ tmp
@@ -0,0 +1,24 @@
1
+ We love pull requests. Here’s a quick guide:
2
+
3
+ 1. Fork the repository.
4
+ 2. Make your changes in a topic branch.
5
+ 3. Squash your commits into a single one (more on that [here](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html)).
6
+ 4. Rebase against `origin/master`, push to your fork and submit a pull request.
7
+ 5. If you are writing a new feature please add documentation for it by making another pull request to the `gh-pages` branch.
8
+
9
+ At this point you’re waiting on us. We like to at least comment on, if not
10
+ accept, pull requests within three business days (and, typically, one business
11
+ day). We may suggest some changes or improvements or alternatives.
12
+
13
+ Some things that will increase the chance that your pull request is accepted:
14
+
15
+ * Fix a bug, refactor code or expand an existing feature.
16
+ * Use the right syntax and naming conventions.
17
+ * Update parts of the documentation that are affected by your contribution.
18
+
19
+ **Git Commit Messages**
20
+
21
+ * Capitalize your commit messages.
22
+ * Start your message with a verb.
23
+ * Use present tense.
24
+ * Refer to the issue/PR number in your squashed commit message.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright © 2015 [slatestudio, inc.](http://slatestudio.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the “Software”), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,18 @@
1
+ # Character
2
+
3
+ ## A simple and lightweight library for building data management web apps
4
+
5
+ ## The Character family
6
+
7
+ - [Mongosteen](https://github.com/slate-studio/mongosteen): An easy way to add restful actions
8
+ - [Character Admin](https://github.com/slate-studio/chr-admin): Flexible javascript admin solution for Rails applications
9
+
10
+ ## Credits
11
+
12
+ [![Slate Studio](https://slate-git-images.s3-us-west-1.amazonaws.com/slate.png)](http://slatestudio.com)
13
+
14
+ 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.
15
+
16
+ ## License
17
+
18
+ Copyright © 2015 [Slate Studio, LLC](http://slatestudio.com). Character is free software, and may be redistributed under the terms specified in the [license](LICENSE.md).
@@ -0,0 +1,3 @@
1
+ # encoding: utf-8
2
+ require "rubygems"
3
+ require "bundler"
@@ -0,0 +1,41 @@
1
+ #= require jquery
2
+ #= require jquery_ujs
3
+ #= require ./chr/vendor/jquery.scrollparent
4
+ #= require ./chr/vendor/jquery.textarea_autosize
5
+ #= require ./chr/vendor/jquery.typeahead
6
+ #= require ./chr/vendor/slip
7
+
8
+ #= require ./chr/core/_item
9
+ #= require ./chr/core/_list
10
+ #= require ./chr/core/_listSearch
11
+ #= require ./chr/core/_listScroll
12
+ #= require ./chr/core/_listReorder
13
+ #= require ./chr/core/_view
14
+ #= require ./chr/core/_module
15
+
16
+ #= require ./chr/form/_form
17
+ #= require ./chr/form/_inputString
18
+ #= require ./chr/form/_inputCheckbox
19
+ #= require ./chr/form/_inputColor
20
+ #= require ./chr/form/_inputFile
21
+ #= require ./chr/form/_inputHidden
22
+ #= require ./chr/form/_inputList
23
+ #= require ./chr/form/_inputSelect
24
+ #= require ./chr/form/_inputText
25
+ #= require ./chr/form/_nestedForm
26
+
27
+ #= require ./chr/store/_store
28
+ #= require ./chr/store/_storeRails
29
+
30
+ #= require ./chr/core/_utils
31
+ #= require ./chr/core/_chr
32
+
33
+ # -------------------------------------
34
+ # Settings
35
+ # -------------------------------------
36
+ $.fx.speeds._default = 200
37
+
38
+
39
+
40
+
41
+
@@ -0,0 +1,89 @@
1
+ # -----------------------------------------------------------------------------
2
+ # Character
3
+ # -----------------------------------------------------------------------------
4
+ class @Chr
5
+ constructor: (@config) ->
6
+ @modules = {}
7
+
8
+ @$el =$ (@config.selector ? 'body')
9
+ @$navBar =$ "<nav class='sidebar'>"
10
+ @$mainMenu =$ "<div class='menu'>"
11
+
12
+ @$navBar.append @$mainMenu
13
+ @$el.append @$navBar
14
+
15
+ @modules[name] = new Module(this, name, config) for name, config of @config.modules
16
+
17
+ # NAVIGATION
18
+ # using class 'silent' for links when we don't want to trigger onhashchange event
19
+ $(document).on 'click', 'a.silent', (e) -> window._skipHashchange = true
20
+
21
+ window.onhashchange = =>
22
+ if not window._skipHashchange then @_navigate(location.hash)
23
+ window._skipHashchange = false
24
+
25
+ # if not mobile navigate on first page load or page refresh
26
+ if not _isMobile()
27
+ window._skipHashchange = false
28
+ @_navigate(if location.hash != '' then location.hash else '#/' + Object.keys(@modules)[0])
29
+
30
+ addMenuItem: (moduleName, title) ->
31
+ @$mainMenu.append "<a href='#/#{ moduleName }'>#{ title }</a>"
32
+
33
+ selectMenuItem: (href) ->
34
+ @$mainMenu.children().removeClass 'active'
35
+ @$mainMenu.children("a[href='#/#{ href }']").addClass 'active'
36
+
37
+ unselectMenuItem: ->
38
+ @$mainMenu.children().removeClass 'active'
39
+
40
+ # TODO: this piece of navigation code isn't clear, need to refactor to make
41
+ # it more readable
42
+ _navigate: (path) ->
43
+ # #/<module>[/<list>][/new]OR[/view/<objectId>]
44
+ crumbs = path.split('/')
45
+
46
+ @unselectMenuItem()
47
+
48
+ # if module changed, hide previous module
49
+ if @module != @modules[crumbs[1]]
50
+ @module?.hide((path == '#/')) # NOTE: animate only for root path
51
+
52
+ @module = @modules[crumbs[1]] # module name on position 1
53
+
54
+ if @module
55
+ @module.show()
56
+
57
+ config = @module.config
58
+ crumbs = crumbs.splice(2) # remove #/<module> part
59
+
60
+ if crumbs.length > 0
61
+ for crumb in crumbs
62
+ if crumb == 'new'
63
+ # TODO: reset list data
64
+ return @module.showView(null, config, 'New')
65
+
66
+ if crumb == 'view'
67
+ objectId = _last(crumbs)
68
+ return @module.showViewWhenObjectsAreReady(objectId, config)
69
+
70
+ config = config.items[crumb]
71
+
72
+ if config.objectStore
73
+ # TODO: check if object is loaded and if it's not load it first
74
+ object = $.extend({ _id: crumb }, config.objectStore.get())
75
+ return @module.showView(object, config, crumb.titleize())
76
+
77
+ else
78
+ @module.showNestedList(crumb)
79
+ else
80
+ # NOTE: show module root list for the case when same module picked
81
+ @module.destroyView()
82
+ while @module.activeList != @module.rootList
83
+ @module.hideActiveList(false)
84
+
85
+
86
+
87
+
88
+
89
+
@@ -0,0 +1,85 @@
1
+ # -----------------------------------------------------------------------------
2
+ # ITEM
3
+ # -----------------------------------------------------------------------------
4
+ class @Item
5
+ _isFolder: ->
6
+ # TODO: update this logic as it's not reliable
7
+ if @object._title then true else false
8
+
9
+ _renderTitle: ->
10
+ title = @object._title # nested list title predefined in config (or slug based)
11
+ title ?= @object[@config.itemTitleField] # based on config
12
+ title ?= _firstNonEmptyValue(@object) # auto-generated: first non empty value
13
+ title ?= "No Title"
14
+ title = _stripHtml(title)
15
+
16
+ @$title =$ "<div class='item-title'>#{title}</div>"
17
+ @$el.append(@$title)
18
+ @$el.attr 'data-title', title
19
+
20
+ _renderSubtitle: ->
21
+ if @config.itemSubtitleField
22
+ subtitle = @object[@config.itemSubtitleField]
23
+ if subtitle != ''
24
+ @$subtitle =$ "<div class='item-subtitle'>#{subtitle}</div>"
25
+ @$el.append(@$subtitle)
26
+ @$el.addClass 'has-subtitle'
27
+
28
+ _renderThumbnail: ->
29
+ if @config.itemThumbnail
30
+ imageUrl = @config.itemThumbnail(@object)
31
+ if imageUrl != '' and not imageUrl.endsWith('_old_') # NOTE: carrierwave fix
32
+ @$thumbnail =$ "<div class='item-thumbnail'><img src='#{imageUrl}' /></div>"
33
+ @$el.append(@$thumbnail)
34
+ @$el.addClass 'has-thumbnail'
35
+
36
+ render: ->
37
+ @$el.html('').removeClass('item-folder has-subtitle has-thumbnail')
38
+ @_renderTitle()
39
+
40
+ if @_isFolder()
41
+ @$el.addClass('item-folder')
42
+ @$el.append $("<div class='icon-folder'></div>")
43
+ else
44
+ @_renderSubtitle()
45
+ @_renderThumbnail()
46
+
47
+ if @config.arrayStore and @config.arrayStore.reorderable
48
+ @$el.addClass('reorderable')
49
+ @$el.append $("<div class='icon-reorder'></div>")
50
+
51
+ constructor: (@module, @path, @object, @config) ->
52
+ @$el =$ """<a class='item silent' href='#{ @path }' data-id='#{ @object._id }' data-title=''></a>"""
53
+ @$el.on 'click', (e) => @onClick(e)
54
+ @render()
55
+
56
+ onClick: (e) ->
57
+ window._skipHashchange = true
58
+ location.hash = $(e.currentTarget).attr('href')
59
+
60
+ title = $(e.currentTarget).attr('data-title')
61
+ id = $(e.currentTarget).attr('data-id')
62
+ crumbs = location.href.split('/')
63
+
64
+ if @config.arrayStore and crumbs[crumbs.length - 2] == 'view'
65
+ object = @config.arrayStore.get(id)
66
+
67
+ if @config.objectStore
68
+ object = @config.objectStore.get()
69
+
70
+ if object
71
+ return @module.showView(object, @config, title, true)
72
+
73
+ @module.showNestedList(_last(crumbs), true)
74
+
75
+ destroy: ->
76
+ @$el.remove()
77
+
78
+ position: ->
79
+ fieldName = @config.arrayStore.sortBy
80
+ @object[fieldName]
81
+
82
+
83
+
84
+
85
+
@@ -0,0 +1,154 @@
1
+ # -----------------------------------------------------------------------------
2
+ # LIST
3
+ # -----------------------------------------------------------------------------
4
+ class @List
5
+ _loading: (callback) ->
6
+ @$el.addClass 'list-loading'
7
+ callback()
8
+
9
+ _path: ->
10
+ crumbs = [] ; l = this
11
+ while l.parentList
12
+ crumbs.push(l.name) ; l = l.parentList
13
+ @module.name + ( if crumbs.length > 0 then '/' + crumbs.reverse().join('/') else '' )
14
+
15
+ _updateItemPosition: (item, position) ->
16
+ position = @configItemsCount + position
17
+ if position == 0
18
+ @$items.prepend(item.$el)
19
+ else
20
+ @$items.append(item.$el.hide())
21
+ $(@$items.children()[position - 1]).after(item.$el.show())
22
+
23
+ _addItem: (path, object, position, config) ->
24
+ item = new Item(@module, path, object, config)
25
+ @items[object._id] = item
26
+ @_updateItemPosition(item, position)
27
+
28
+ _processConfigItems: ->
29
+ for slug, config of @config.items
30
+ object = { _id: slug, _title: config.title ? slug.titleize() }
31
+
32
+ if config.objectStore
33
+ $.extend(object, config.objectStore.get())
34
+
35
+ if config.items or config.arrayStore
36
+ @module.addNestedList(slug, config, this)
37
+
38
+ @_addItem("#/#{ @path }/#{ slug }", object, 0, config)
39
+ @configItemsCount += 1
40
+
41
+ _bindConfigObjectStore: ->
42
+
43
+ _bindConfigArrayStore: ->
44
+
45
+ # NOTE: starts data fetch
46
+ @config.arrayStore.on 'object_added', (e, data) =>
47
+ @_addItem("#/#{ @path }/view/#{ data.object._id }", data.object, data.position, @config)
48
+ data.callback?(data.object)
49
+
50
+ @config.arrayStore.on 'object_changed', (e, data) =>
51
+ item = @items[data.object._id]
52
+ item.render()
53
+ @_updateItemPosition(item, data.position)
54
+ data.callback?(data.object)
55
+
56
+ @config.arrayStore.on 'object_removed', (e, data) =>
57
+ @items[data.object_id].destroy()
58
+ delete @items[data.object_id]
59
+
60
+ @config.arrayStore.on 'objects_added', (e, data) =>
61
+ @$el.removeClass 'list-loading'
62
+
63
+ if @config.arrayStore.pagination
64
+ _listBindScroll(this)
65
+
66
+ if @config.arrayStore.searchable
67
+ _listBindSearch(this)
68
+
69
+ if @config.arrayStore.reorderable
70
+ _listBindReorder(this)
71
+
72
+ constructor: (@module, @name, @config, @parentList) ->
73
+ @configItemsCount = 0
74
+ @path = @_path()
75
+ @items = {}
76
+ @title = @config.title ? @name.titleize()
77
+
78
+ @$el =$ "<div class='list #{ @name }'>"
79
+ @module.$el.append @$el
80
+
81
+ if @parentList then @$el.hide() # hide all nested lists
82
+
83
+ @$items =$ "<div class='items'>"
84
+ @$el.append @$items
85
+
86
+ @$header =$ "<header></header>"
87
+
88
+ if @parentList
89
+ # NOTE: show back button for nested list
90
+ @parentListPath = @parentList.path
91
+ @$backBtn =$ "<a href='#/#{ @parentListPath }' class='back silent'></a>"
92
+ @$backBtn.on 'click', (e) => @onBack(e)
93
+ @$header.prepend @$backBtn
94
+ else
95
+ @$backBtn =$ "<a href='#/' class='back'></a>"
96
+ @$header.prepend @$backBtn
97
+
98
+
99
+ @$header.append "<span class='title'>#{ @title }</span>"
100
+
101
+ if not @config.disableNewItems and @config.formSchema
102
+ @$newBtn =$ "<a href='#/#{ @path }/new' class='new silent'></a>"
103
+ @$newBtn.on 'click', (e) => @onNew(e)
104
+ @$header.append @$newBtn
105
+
106
+ @$search =$ """<div class='search' style='display: none;'>
107
+ <a href='#' class='icon'></a>
108
+ <input type='text' placeholder='Search...' />
109
+ <a href='#' class='cancel'>Cancel</a>
110
+ </div>"""
111
+ @$header.append @$search
112
+
113
+ @$el.append @$header
114
+
115
+ if @config.items then @_processConfigItems()
116
+ if @config.arrayStore then @_bindConfigArrayStore()
117
+ if @config.objectStore then @_bindConfigObjectStore()
118
+
119
+ selectItem: (href) ->
120
+ @$items.children("a[href='#{ href }']").addClass 'active'
121
+
122
+ unselectItems: ->
123
+ @$items.children().removeClass 'active'
124
+
125
+ hide: ->
126
+ @unselectItems()
127
+ @$el.hide()
128
+
129
+ show: (animate=false) ->
130
+ if animate
131
+ @$el.fadeIn()
132
+ else
133
+ @$el.show()
134
+
135
+ onBack: (e) ->
136
+ @unselectItems()
137
+ @module.hideActiveList(true)
138
+ @module.destroyView()
139
+
140
+ onNew: (e) ->
141
+ window._skipHashchange = true
142
+ location.hash = $(e.currentTarget).attr('href')
143
+ @module.showView(null, @config, 'New', true)
144
+
145
+ updateItems: (callback) ->
146
+ if not @config.disableReset
147
+ if @config.arrayStore
148
+ @_loading => @config.arrayStore.reset(callback)
149
+
150
+ isVisible: ->
151
+ @$el.is(':visible')
152
+
153
+
154
+