nail_polish 0.1.1 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +2 -4
  3. data/app/assets/images/nail_polish/elements/link_action.png +0 -0
  4. data/app/assets/images/nail_polish/elements/white_down_arrow.png +0 -0
  5. data/app/assets/javascripts/nail_polish.js +6 -1
  6. data/app/assets/javascripts/nail_polish/app.js +32 -18
  7. data/app/assets/javascripts/nail_polish/events.js +2 -0
  8. data/app/assets/javascripts/nail_polish/model.js +49 -0
  9. data/app/assets/javascripts/nail_polish/models/dropdown.js +9 -0
  10. data/app/assets/javascripts/nail_polish/models/menu.js +1 -0
  11. data/app/assets/javascripts/nail_polish/nail_polish.js +2 -0
  12. data/app/assets/javascripts/nail_polish/presenter.js +34 -7
  13. data/app/assets/javascripts/nail_polish/presenter_with_errors.js +61 -0
  14. data/app/assets/javascripts/nail_polish/presenters/dropdown.js +7 -12
  15. data/app/assets/javascripts/nail_polish/presenters/menu.js +7 -0
  16. data/app/assets/javascripts/nail_polish/router.js +23 -13
  17. data/app/assets/javascripts/nail_polish/templates/dropdown.mustache +14 -0
  18. data/app/assets/javascripts/nail_polish/templates/menu.mustache +22 -0
  19. data/app/assets/javascripts/nail_polish/templates/modal.mustache +5 -0
  20. data/app/assets/javascripts/nail_polish/utils/subview_manager.js +28 -0
  21. data/app/assets/javascripts/nail_polish/validator.js +147 -0
  22. data/app/assets/javascripts/nail_polish/view.js +17 -8
  23. data/app/assets/javascripts/nail_polish/views/form.js +44 -0
  24. data/app/assets/javascripts/nail_polish/widget/dropdown.js +37 -9
  25. data/app/assets/javascripts/nail_polish/widget/flash.js +8 -8
  26. data/app/assets/javascripts/nail_polish/widget/menu.js +61 -0
  27. data/app/assets/javascripts/nail_polish/widget/modal.js +35 -5
  28. data/app/assets/javascripts/nail_polish_widgets.js +1 -0
  29. data/app/assets/stylesheets/nail_polish/base.scss +1 -1
  30. data/app/assets/stylesheets/nail_polish/base/buttons.scss +5 -0
  31. data/app/assets/stylesheets/nail_polish/base/form_elements.scss +15 -1
  32. data/app/assets/stylesheets/nail_polish/base/grid.scss +8 -6
  33. data/app/assets/stylesheets/nail_polish/base/layout.scss +3 -2
  34. data/app/assets/stylesheets/nail_polish/base/oo_styles.scss +37 -2
  35. data/app/assets/stylesheets/nail_polish/base/reset.scss +5 -0
  36. data/app/assets/stylesheets/nail_polish/base/typography_and_tags.scss +4 -0
  37. data/app/assets/stylesheets/nail_polish/desktop.scss +1 -1
  38. data/app/assets/stylesheets/nail_polish/desktop/grid.scss +6 -1
  39. data/app/assets/stylesheets/nail_polish/desktop/widgets/menu.scss +61 -0
  40. data/app/assets/stylesheets/nail_polish/tablet.scss +0 -1
  41. data/app/assets/stylesheets/nail_polish/tablet/grid.scss +3 -0
  42. data/lib/nail_polish/version.rb +1 -1
  43. metadata +43 -14
@@ -0,0 +1,14 @@
1
+ <div class="row dropdown-toggle {{name}}-dropdown-toggle">
2
+ <div class="unit">
3
+ <div id="{{name}}" class="dropdown-selected-option" data-option='{{selected_key}}'>
4
+ {{selected_value}}
5
+ </div>
6
+ </div>
7
+ <div class="unit-right caret"></div>
8
+ </div>
9
+
10
+ <ul class="dropdown-menu {{name}}-dropdown-menu hidden">
11
+ {{#selectable_items}}
12
+ <li class="menu-item" data-option={{key}}>{{value}}</li>
13
+ {{/selectable_items}}
14
+ </ul>
@@ -0,0 +1,22 @@
1
+ <div class='inner-right menu-item-wrapper'>
2
+ <div class='menu-name'>
3
+ <a href='javascript:void(0);' class="small menu-trigger button">
4
+ <span class="white-down-arrow">{{trigger_link_text}}</span>
5
+ </a>
6
+ </div>
7
+
8
+ <div class='menu-items hidden'>
9
+ <div class='menu-caret'></div>
10
+ {{#items}}
11
+ <div class='row menu-item'>
12
+ <div class='inner half'>
13
+ <a href="{{path}}">
14
+ <h3 class="icon {{icon_class}}">
15
+ {{link_text}}
16
+ </h3>
17
+ </a>
18
+ </div>
19
+ </div>
20
+ {{/items}}
21
+ </div>
22
+ </div>
@@ -0,0 +1,5 @@
1
+ <div id='overlay'>
2
+ <div class='modal l-modal'>
3
+ {{>modal_content}}
4
+ </div>
5
+ </div>
@@ -0,0 +1,28 @@
1
+ NailPolish.SubviewManager = {
2
+ renderEach: function (subviews) {
3
+ this._subviews = subviews;
4
+
5
+ _.each(subviews, function (view) {
6
+ if(view) {
7
+ view.parent = view.parent || this.defaultParent();
8
+ view.repository = view.repository || this.repository;
9
+ view.render();
10
+ }
11
+ }.bind(this));
12
+ },
13
+
14
+ remove: function() {
15
+ this.removeSelf();
16
+
17
+ _.each(this._subviews, function(subview) {
18
+ if (subview){
19
+ subview.remove();
20
+ }
21
+ });
22
+ },
23
+
24
+ removeSelf: function() { /* override me */ },
25
+ defaultParent: function() {
26
+ return this;
27
+ }
28
+ };
@@ -0,0 +1,147 @@
1
+ /*
2
+ Provides errors object builder for Backbone.js models.
3
+ Follows Backbone.js validation conventions.
4
+ */
5
+
6
+ NailPolish.Validator = function() {
7
+ this._attributeRuleSets = {};
8
+ };
9
+
10
+ /*
11
+ Starts the validations chain for a particular attribute(s).
12
+ See documentation for `NailPolish.Validator.RuleSet.prototype.addRule` below.
13
+ */
14
+ NailPolish.Validator.prototype.attribute = function() {
15
+ var ruleSet = new NailPolish.Validator.RuleSet();
16
+
17
+ _.each(arguments, function(attributeName) {
18
+ var attributeRuleSet = this._attributeRuleSets[attributeName] ||
19
+ new NailPolish.Validator.RuleSet();
20
+ attributeRuleSet.chain(ruleSet);
21
+ this._attributeRuleSets[attributeName] = attributeRuleSet;
22
+ }, this);
23
+
24
+ return ruleSet;
25
+ };
26
+
27
+ /*
28
+ Returns an object containing errors or `undefined` if there were none.
29
+
30
+ Example:
31
+
32
+ validate.validate()
33
+
34
+ // {
35
+ // "username" : ["is too short", "must not contain whitespace"],
36
+ // "age": ["is not a number"]
37
+ // }
38
+ */
39
+ NailPolish.Validator.prototype.validate = function(attributes) {
40
+ var errors = {};
41
+
42
+ _.each(attributes, function(attributeValue, attributeName) {
43
+ var ruleSet = this._attributeRuleSets[attributeName];
44
+ if (!ruleSet) return;
45
+
46
+ _.each(ruleSet.rules(), function(rule) {
47
+ if (rule(attributeValue, attributeName)) {
48
+ errors[attributeName] = errors[attributeName] || [];
49
+ errors[attributeName].push(rule.message);
50
+ }
51
+ }, this);
52
+ }, this);
53
+
54
+ return _.isEmpty(errors) ? undefined : errors;
55
+ };
56
+
57
+ /*
58
+ Built-in validations.
59
+
60
+ Usage:
61
+
62
+ var validate = new NailPolish.Validator();
63
+ validate.
64
+ attribute("username").
65
+ addRule("is too short", validate.is.tooShort(5));
66
+
67
+ Extending:
68
+
69
+ _.extend(NailPolish.Validator.prototype.is, {
70
+ whitespaceFree: function() {
71
+ return function(value) { return undefined !== value && value.match(/\s/); }
72
+ }
73
+ });
74
+
75
+ ...
76
+
77
+ validate.
78
+ attribute("username").
79
+ addRule("must not contain whitespace", validate.is.whitespaceFree());
80
+ */
81
+ (function() {
82
+ var notEmpty = function(value) {
83
+ return null != value && "" !== value;
84
+ };
85
+
86
+ NailPolish.Validator.prototype.is = {
87
+ required: function() {
88
+ return function(value) { return !notEmpty(value); };
89
+ },
90
+
91
+ tooShort: function(minLength) {
92
+ return function(value) { return notEmpty(value) && value.length < minLength; };
93
+ },
94
+
95
+ tooLong: function(maxLength) {
96
+ return function(value) { return notEmpty(value) && value.length > maxLength; };
97
+ },
98
+
99
+ notMatching: function(pattern) {
100
+ return function(value) { return notEmpty(value) && !String(value).match(pattern); }
101
+ }
102
+ };
103
+ })();
104
+
105
+ NailPolish.Validator.RuleSet = function() {
106
+ this._rules = [];
107
+ this._chainedRuleSets = [];
108
+ };
109
+
110
+ /*
111
+ Defines validation rule and an error message for a particular attribute(s).
112
+
113
+ Attribute is considered to be *invalid* if the validation function returns a truthy value.
114
+
115
+ Multiple calls to `addRule` can be chained to add as many validations as needed.
116
+
117
+ In case of custom validation functions, it's up to you to make sure that
118
+ validation functions don't depend on each other and are capable of handling
119
+ any *possible* input, be it `null`, `undefined`, or an actual value.
120
+
121
+ Usage:
122
+
123
+ validate.
124
+ attribute("username", "email").
125
+ addRule("is already in use", function(attrValue, attrName) {
126
+ return this.collection.detect(function(user) {
127
+ return user.get(attrName) == attrValue;
128
+ });
129
+ }.bind(this));
130
+ */
131
+ NailPolish.Validator.RuleSet.prototype.addRule = function(errorMessage, validatingFunc) {
132
+ validatingFunc.message = errorMessage;
133
+ this._rules.push(validatingFunc);
134
+ return this;
135
+ }
136
+
137
+ /* private */
138
+ NailPolish.Validator.RuleSet.prototype.chain = function(ruleSet) {
139
+ this._chainedRuleSets.push(ruleSet);
140
+ }
141
+
142
+ /* private */
143
+ NailPolish.Validator.RuleSet.prototype.rules = function() {
144
+ return _.reduce(this._chainedRuleSets, function(memo, ruleSet) {
145
+ return memo.concat(ruleSet.rules());
146
+ }, this._rules);
147
+ }
@@ -1,4 +1,4 @@
1
- NailPolish.View = Backbone.View.extend({
1
+ NailPolish.View = Backbone.View.extend(_.extend(_.clone(NailPolish.SubviewManager), {
2
2
  addListeners: {},
3
3
  presenterClass: function () {
4
4
  return NailPolish.Presenter;
@@ -8,7 +8,14 @@ NailPolish.View = Backbone.View.extend({
8
8
  opts = opts || {};
9
9
  this.parent = (this.parent && $(this.parent)) || opts.parent;
10
10
  this.repository = opts.repository;
11
- this.attachmentMethod = this.attachmentMethod || 'append';
11
+ this.attachmentMethod = opts.attachmentMethod || this.attachmentMethod || 'append';
12
+ this.parentSelector = opts.parentSelector || this.parentSelector;
13
+ this.templateName = opts.templateName || this.templateName;
14
+ if (opts.presenterClass) {
15
+ this.presenterClass = function() {
16
+ return opts.presenterClass;
17
+ };
18
+ }
12
19
  this.init(opts);
13
20
  },
14
21
 
@@ -27,11 +34,8 @@ NailPolish.View = Backbone.View.extend({
27
34
  return [];
28
35
  },
29
36
 
30
- renderSubviews: function () {
31
- _.each(this.subviews(), function (view) {
32
- view.parent = view.parent || this;
33
- view.render();
34
- }.bind(this));
37
+ renderSubviews: function() {
38
+ this.renderEach(this.subviews());
35
39
  },
36
40
 
37
41
  attachToParent: function () {
@@ -70,11 +74,16 @@ NailPolish.View = Backbone.View.extend({
70
74
  presenter: function () {
71
75
  var presented = this.model || this.collection || {};
72
76
  var presenter = new (this.presenterClass())(presented);
77
+ presenter.repository = presenter.repository || this.repository;
73
78
  return presenter.toJSON();
74
79
  },
75
80
 
76
81
  partials: function () {
77
82
  // override with own partials if necessary
78
83
  return {};
84
+ },
85
+
86
+ removeSelf: function() {
87
+ Backbone.View.prototype.remove.apply(this);
79
88
  }
80
- });
89
+ }));
@@ -0,0 +1,44 @@
1
+ NailPolish.Views.Form = NailPolish.View.extend({
2
+ formAttrs: [], /* implement in subclass */
3
+
4
+ init: function(){
5
+ this.listenTo(this.model, 'invalid', this.addErrors);
6
+ },
7
+
8
+ readAttributes: function() {
9
+ var attrs = {};
10
+
11
+ _.each(this.formAttrs, function(name) {
12
+ attrs[name] = this.readAttribute(name);
13
+ }.bind(this));
14
+
15
+ _.extend(attrs, this.readAdditionalAttributes());
16
+
17
+ if(this.model.id) { _.extend(attrs, {'_method' : 'put'}); }
18
+ return attrs;
19
+ },
20
+
21
+ readAttribute: function(attr) {
22
+ return this.$('[name=' + attr + ']').val();
23
+ },
24
+
25
+ readAdditionalAttributes: function() {
26
+ return {}; /* override for additional form stuff */
27
+ },
28
+
29
+ addErrors: function() {
30
+ var errorKeys = _.keys(this.model.validationError);
31
+ var selector = _.map(errorKeys, function(key) {
32
+ return ".input-row." + key;
33
+ }).join(', ');
34
+ this.$(selector).addClass('error');
35
+ },
36
+
37
+ clearErrors: function() {
38
+ _.each(this.formAttrs, function(key) {
39
+ this.model.set(key + '_error', undefined);
40
+ }.bind(this));
41
+
42
+ this.$('.input-row').removeClass('error');
43
+ }
44
+ });
@@ -1,22 +1,48 @@
1
1
  NailPolish.Widget.Dropdown = NailPolish.View.extend({
2
- templateName: 'templates/dropdown',
2
+ templateName: 'nail_polish/templates/dropdown',
3
3
 
4
4
  events: {
5
5
  'click .dropdown-toggle': 'toggle',
6
- 'click .menu-item': 'setSelected'
6
+ 'click .menu-item': 'setSelected',
7
+ 'click .dropdown-menu': 'stopPropagation'
7
8
  },
8
9
 
10
+ hiddenClass: 'hidden',
11
+
9
12
  init: function(){
10
- this.className = this.model.name + '-drop-down';
11
- this.menuSelector = '.' + this.model.name + '-dropdown-menu';
13
+ this.className = this.model.get('name') + '-drop-down';
14
+ this.menuSelector = '.' + this.model.get('name') + '-dropdown-menu';
12
15
  },
13
16
 
14
17
  presenterClass: function () {
15
18
  return NailPolish.Presenter.Dropdown;
16
19
  },
17
20
 
18
- toggle: function() {
19
- $(this.menuSelector).toggleClass(this.hiddenClass);
21
+ toggle: function(e) {
22
+ if (this.menuIsVisible()) {
23
+ this.hideMenu.bind(this)();
24
+ } else {
25
+ this.showMenu();
26
+ e.stopPropagation();
27
+ }
28
+ },
29
+
30
+ menuIsVisible: function() {
31
+ return this.$(this.menuSelector).is(':visible');
32
+ },
33
+
34
+ hideMenu: function() {
35
+ this.$el.find(this.menuSelector).addClass(this.hiddenClass);
36
+ this.removeBodyListener();
37
+ },
38
+
39
+ showMenu: function() {
40
+ this.$el.find(this.menuSelector).removeClass(this.hiddenClass);
41
+ $('body').on(NailPolish.Events.click, this.hideMenu.bind(this));
42
+ },
43
+
44
+ removeBodyListener: function() {
45
+ $('body').off(NailPolish.Events.click, this.hideMenu.bind(this));
20
46
  },
21
47
 
22
48
  renderTemplate: function () {
@@ -26,15 +52,17 @@ NailPolish.Widget.Dropdown = NailPolish.View.extend({
26
52
  this.$el.html(rendered);
27
53
  },
28
54
 
29
- hiddenClass: 'hidden',
30
-
31
55
  setSelected: function(e){
32
56
  var $target = $(e.target);
33
- this.model.selected = $target.attr("data-option");
57
+ this.model.set('selected_key', $target.attr("data-option"));
34
58
  this.render();
35
59
  this.afterSelect();
36
60
  },
37
61
 
62
+ stopPropagation: function(e) {
63
+ e.stopPropagation();
64
+ },
65
+
38
66
  afterSelect: function() {
39
67
  //overwrite me in subclass if you need to do stuffs
40
68
  },
@@ -3,7 +3,7 @@ NailPolish.Widget.Flash = NailPolish.View.extend({
3
3
  className: 'flash',
4
4
  parentSelector: '#flash-container',
5
5
  attachmentMethod: 'html',
6
- animationLength: 500,
6
+ animationLength: 1000,
7
7
  showFor: 2000,
8
8
 
9
9
  addListeners: {
@@ -20,17 +20,17 @@ NailPolish.Widget.Flash = NailPolish.View.extend({
20
20
  },
21
21
 
22
22
  show: function () {
23
- window.scrollTo(0, 0);
24
- this.$el.css('opacity', 0);
25
23
  this.$el.show();
26
- this.$el.animate({opacity: 1}, this.animationLength);
27
-
24
+ this.$el.find('.flash').css('opacity', 1);
28
25
  setTimeout(this.hide.bind(this), this.showFor);
29
26
  },
30
27
 
31
28
  hide: function () {
32
- this.$el.animate(
33
- {opacity: 0}, this.animationLength, this.$el.hide.bind(this.$el)
34
- );
29
+ this.$el.find('.flash').css('opacity', 0);
30
+ setTimeout(this.removeFlash.bind(this), this.animationLength);
31
+ },
32
+
33
+ removeFlash: function() {
34
+ this.$el.hide();
35
35
  }
36
36
  });
@@ -0,0 +1,61 @@
1
+ NailPolish.Widget.Menu = NailPolish.View.extend({
2
+ parentSelector: '.menu-container',
3
+ templateName: 'nail_polish/templates/menu',
4
+ addListeners: {
5
+ "click .menu-trigger": 'toggleMenu',
6
+ "click .menu-item a": 'menuItemClick'
7
+ },
8
+
9
+ onMenuItemClick: function (e) {
10
+ // hook in here
11
+ },
12
+
13
+ presenterClass: function() {
14
+ return NailPolish.Presenter.Menu;
15
+ },
16
+
17
+ toggleMenu: function(e) {
18
+ if (this.menuIsVisible()) {
19
+ this.hideMenu();
20
+ } else {
21
+ this.hideAllMenus();
22
+ this.showMenu(e);
23
+ }
24
+ },
25
+
26
+ menuIsVisible: function() {
27
+ return this.$('.menu-items').is(':visible');
28
+ },
29
+
30
+ showMenu: function(e) {
31
+ this.$('.menu-trigger').addClass("selected");
32
+ this.$('.menu-items').css("display", "block");
33
+ this.onBodyClick = this.hideMenu.bind(this);
34
+ $('body').on(NailPolish.Events.click, this.onBodyClick);
35
+ e.stopPropagation();
36
+ e.preventDefault();
37
+ },
38
+
39
+ hideMenu: function() {
40
+ this.$('.menu-trigger').removeClass("selected");
41
+ this.$('.menu-items').css("display", "none");
42
+ this.removeBodyListener();
43
+ },
44
+
45
+ hideAllMenus: function() {
46
+ $('.menu-trigger').removeClass("selected");
47
+ $('.menu-items').css("display", "none");
48
+ },
49
+
50
+ removeBodyListener: function() {
51
+ if(this.onBodyClick){
52
+ $('body').off(NailPolish.Events.click, this.onBodyClick);
53
+ this.onBodyClick = undefined;
54
+ }
55
+ },
56
+
57
+ menuItemClick: function(e) {
58
+ this.onMenuItemClick(e);
59
+ this.hideMenu();
60
+ }
61
+ });