Skip to content

Commit

Permalink
groundwork for creating a jquery based component
Browse files Browse the repository at this point in the history
  • Loading branch information
Florian Koch committed May 17, 2017
1 parent c852489 commit 9f6833b
Show file tree
Hide file tree
Showing 13 changed files with 274 additions and 13 deletions.
4 changes: 0 additions & 4 deletions README.md
Expand Up @@ -30,10 +30,6 @@ Or install it yourself as:

TODO: provide usage instructions

## Contributing

Contribution directions go here.

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
Expand Down
Expand Up @@ -10,4 +10,7 @@
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require criteria_operator/ui_component/criteria_editor
//= require_tree .
@@ -0,0 +1,232 @@
// ---------------------------------
// ----- Criteria Editor Plugin ----
// ---------------------------------
// Using John Dugan's boilerplate: https://john-dugan.com/jquery-plugin-boilerplate-explained/
// ---------------------------------

/*
The semi-colon before the function invocation is a safety net against
concatenated scripts and/or other plugins which may not be closed properly.
"undefined" is used because the undefined global variable in ECMAScript 3
is mutable (ie. it can be changed by someone else). Because we don't pass a
value to undefined when the anonymyous function is invoked, we ensure that
undefined is truly undefined. Note, in ECMAScript 5 undefined can no
longer be modified.
"window" and "document" are passed as local variables rather than global.
This (slightly) quickens the resolution process.
*/
;(function ( $, window, document, undefined ) {

/*
Store the name of the plugin in the "pluginName" variable. This
variable is used in the "Plugin" constructor below, as well as the
plugin wrapper to construct the key for the "$.data" method.
More: http://api.jquery.com/jquery.data/
*/
var pluginName = 'criteriaEditor';

/*
The "Plugin" constructor, builds a new instance of the plugin for the
DOM node(s) that the plugin is called on. For example,
"$('h1').pluginName();" creates a new instance of pluginName for
all h1's.
*/
// Create the plugin constructor
function Plugin ( element, options ) {
/*
Provide local access to the DOM node(s) that called the plugin,
as well local access to the plugin name and default options.
*/
this.element = element;
this._name = pluginName;
this._defaults = $.fn.myPluginName.defaults;
/*
The "$.extend" method merges the contents of two or more objects,
and stores the result in the first object. The first object is
empty so that we don't alter the default options for future
instances of the plugin.
More: http://api.jquery.com/jquery.extend/
*/
this.options = $.extend( {}, this._defaults, options );

/*
The "init" method is the starting point for all plugin logic.
Calling the init method here in the "Plugin" constructor function
allows us to store all methods (including the init method) in the
plugin's prototype. Storing methods required by the plugin in its
prototype lowers the memory footprint, as each instance of the
plugin does not need to duplicate all of the same methods. Rather,
each instance can inherit the methods from the constructor
function's prototype.
*/
this.init();
}

// Avoid Plugin.prototype conflicts
$.extend(Plugin.prototype, {

// Initialization logic
init: function () {
/*
Create additional methods below and call them via
"this.myFunction(arg1, arg2)", ie: "this.buildCache();".
Note, you can cccess the DOM node(s), plugin name, default
plugin options and custom plugin options for a each instance
of the plugin by using the variables "this.element",
"this._name", "this._defaults" and "this.options" created in
the "Plugin" constructor function (as shown in the buildCache
method below).
*/
this.buildCache();
this.bindEvents();
},

// Remove plugin instance completely
destroy: function() {
/*
The destroy method unbinds all events for the specific instance
of the plugin, then removes all plugin data that was stored in
the plugin instance using jQuery's .removeData method.
Since we store data for each instance of the plugin in its
instantiating element using the $.data method (as explained
in the plugin wrapper below), we can call methods directly on
the instance outside of the plugin initalization, ie:
$('selector').data('plugin_myPluginName').someOtherFunction();
Consequently, the destroy method can be called using:
$('selector').data('plugin_myPluginName').destroy();
*/
this.unbindEvents();
this.$element.removeData();
},

// Cache DOM nodes for performance
buildCache: function () {
/*
Create variable(s) that can be accessed by other plugin
functions. For example, "this.$element = $(this.element);"
will cache a jQuery reference to the element that initialized
the plugin. Cached variables can then be used in other methods.
*/
this.$element = $(this.element);
},

// Bind events that trigger methods
bindEvents: function() {
var plugin = this;

/*
Bind event(s) to handlers that trigger other functions, ie:
"plugin.$element.on('click', function() {});". Note the use of
the cached variable we created in the buildCache method.
All events are namespaced, ie:
".on('click'+'.'+this._name', function() {});".
This allows us to unbind plugin-specific events using the
unbindEvents method below.
*/
plugin.$element.on('click'+'.'+plugin._name, function() {
/*
Use the "call" method so that inside of the method being
called, ie: "someOtherFunction", the "this" keyword refers
to the plugin instance, not the event handler.
More: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
*/
plugin.someOtherFunction.call(plugin);
});
},

// Unbind events that trigger methods
unbindEvents: function() {
/*
Unbind all events in our plugin's namespace that are attached
to "this.$element".
*/
this.$element.off('.'+this._name);
},

/*
"someOtherFunction" is an example of a custom method in your
plugin. Each method should perform a specific task. For example,
the buildCache method exists only to create variables for other
methods to access. The bindEvents method exists only to bind events
to event handlers that trigger other methods. Creating custom
plugin methods this way is less confusing (separation of concerns)
and makes your code easier to test.
*/
// Create custom methods
someOtherFunction: function() {
alert('I promise to do something cool!'); this.callback();
},

callback: function() {
// Cache onComplete option
var onComplete = this.options.onComplete;

if ( typeof onComplete === 'function' ) {
/*
Use the "call" method so that inside of the onComplete
callback function the "this" keyword refers to the
specific DOM node that called the plugin.
More: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
*/
onComplete.call(this.element);
}
}

});

/*
Create a lightweight plugin wrapper around the "Plugin" constructor,
preventing against multiple instantiations.
More: http://learn.jquery.com/plugins/basic-plugin-creation/
*/
$.fn.myPluginName = function ( options ) {
this.each(function() {
if ( !$.data( this, "plugin_" + pluginName ) ) {
/*
Use "$.data" to save each instance of the plugin in case
the user wants to modify it. Using "$.data" in this way
ensures the data is removed when the DOM element(s) are
removed via jQuery methods, as well as when the userleaves
the page. It's a smart way to prevent memory leaks.
More: http://api.jquery.com/jquery.data/
*/
$.data( this, "plugin_" + pluginName, new Plugin( this, options ) );
}
});
/*
"return this;" returns the original jQuery object. This allows
additional jQuery methods to be chained.
*/
return this;
};

/*
Attach the default plugin options directly to the plugin object. This
allows users to override default plugin options globally, instead of
passing the same option(s) every time the plugin is initialized.
For example, the user could set the "property" value once for all
instances of the plugin with
"$.fn.pluginName.defaults.property = 'myValue';". Then, every time
plugin is initialized, "property" will be set to "myValue".
More: http://learn.jquery.com/plugins/advanced-plugin-concepts/
*/
$.fn.myPluginName.defaults = {
property: 'value',
onComplete: null
};

})( jQuery, window, document );
Expand Up @@ -13,3 +13,8 @@
*= require_tree .
*= require_self
*/

.criteria_editor {
border: dashed #2E2F30 thin;
padding: 5px;
}
@@ -0,0 +1,3 @@
<div>
This is a row <%= test %>
</div>
@@ -0,0 +1,4 @@
<div>
<h3>AND / OR</h3>
<%= cell('criteria_operator/ui_component/criteria_editor', model).(:expression_row) %>
</div>
@@ -1,3 +1,4 @@
<div>
<h2><%= test %></h2>h2>
<div class="criteria_editor">
<h2>Editor</h2>
<%= cell('criteria_operator/ui_component/criteria_editor', model).(:group_row) %>
</div>
Expand Up @@ -9,7 +9,15 @@ class CriteriaEditorCell < Cell::ViewModel
self.view_paths << "#{CriteriaOperator::UiComponent::Engine.root}/app/cells"

def show
render 'show'
render
end

def group_row
render
end

def expression_row
render
end

private
Expand Down
1 change: 1 addition & 0 deletions criteria_operator-ui_component.gemspec
Expand Up @@ -44,6 +44,7 @@ Gem::Specification.new do |spec|
spec.add_dependency "cells", "~> 4.1"
spec.add_dependency "cells-rails", "~> 0.0"
spec.add_dependency "cells-erb", "~> 0.1"
spec.add_dependency "jquery-rails", "~> 4.3"
spec.add_dependency "criteria_operator", "~> 0.2"

# let yard run on install
Expand Down
6 changes: 4 additions & 2 deletions lib/criteria_operator/ui_component/engine.rb
@@ -1,10 +1,12 @@
module CriteriaOperator
module UiComponent
class Engine < ::Rails::Engine
require 'jquery-rails'
require 'criteria_operator'
isolate_namespace CriteriaOperator::UiComponent

# config.assets.paths << File.expand_path("../../../assets/stylesheets/application", __FILE__)
# config.assets.paths << File.expand_path("../../../assets/javascripts/application", __FILE__)
config.assets.paths << File.expand_path("../../../app/assets/stylesheets/application", __FILE__)
config.assets.paths << File.expand_path("../../../app/assets/javascripts/application", __FILE__)
end
end
end
8 changes: 5 additions & 3 deletions test/dummy/app/controllers/dummy_controller.rb
@@ -1,7 +1,9 @@
class DummyController < ApplicationController
def show
@whatever = 42

render { CriteriaOperator::UiComponent::CriteriaEditorCell.(@whatever).() }
first_op = CriteriaOperator::UnaryOperator.new(CriteriaOperator::OperandValue.new(true), CriteriaOperator::UnaryOperatorType::NOT)
second_op = CriteriaOperator::BinaryOperator.new(CriteriaOperator::OperandProperty.new("column xyz"), CriteriaOperator::OperandValue.new(42), CriteriaOperator::BinaryOperatorType::GREATER_OR_EQUAL)
@example_op = CriteriaOperator::GroupOperator.new [first_op, second_op], CriteriaOperator::GroupOperatorType::OR
@cell_obj = CriteriaOperator::UiComponent::CriteriaEditorCell.(@example_op)
render
end
end
4 changes: 3 additions & 1 deletion test/dummy/app/views/dummy/show.html.erb
@@ -1 +1,3 @@
<%= yield %>
<h2>This is a dummy page</h2>
<%= raw @cell_obj.() %>
<%# cell('criteria_operator/ui_component/criteria_editor', @example_op) %>
2 changes: 2 additions & 0 deletions test/dummy/app/views/layouts/application.html.erb
Expand Up @@ -5,7 +5,9 @@
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= stylesheet_link_tag 'criteria_operator/ui_component/application' %>
<%= javascript_include_tag 'application' %>
<%= javascript_include_tag 'criteria_operator/ui_component/application' %>
</head>

<body>
Expand Down

0 comments on commit 9f6833b

Please sign in to comment.