├── .gitignore ├── CHANGELOG.md ├── Field ├── Hidden.php ├── Checkbox.php ├── Text.php ├── Textarea.php ├── WP_Editor.php ├── Select.php ├── Radio.php ├── Fieldset.php ├── Image.php ├── map.php └── Collection.php ├── Resources ├── js │ ├── Field │ │ ├── Text.js │ │ ├── Checkbox.js │ │ ├── Radio.js │ │ ├── Textarea.js │ │ ├── Select.js │ │ ├── Hidden.js │ │ ├── Fieldset.js │ │ ├── WP_Editor.js │ │ ├── Image.js │ │ ├── map.js │ │ └── Collection.js │ ├── Metabox.js │ ├── ob.js │ └── Field.js └── css │ └── ob.css ├── LICENSE ├── README.md ├── Builder.php ├── Custom_Taxonomy.php ├── core-post-type.php ├── Custom_Meta_Box.php ├── Custom_Form_Element.php ├── Custom_Post_Type.php ├── Custom_Field.php └── Inflector.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [0.2] - 2016-04-16 6 | ### Added 7 | - Image, WP Editor, Hidden and Map fields 8 | - Core Post Type class 9 | - Collection Blocks 10 | 11 | ### Changed 12 | - Style of all fields 13 | - Fields rendering 14 | - A lot of bugfixes and improvements 15 | 16 | ## [0.1] - 2014-11-25 17 | ### Added 18 | - Initial github release -------------------------------------------------------------------------------- /Field/Hidden.php: -------------------------------------------------------------------------------- 1 | 18 | 21 | 23 | 31 | 23 | 30 | 23 | 30 | 17 | 41 | options['choices'] ) && ! empty( $this->options['choices'] ) ) 25 | $this->json_data['choices'] = $this->options['choices']; 26 | 27 | return $this; 28 | } 29 | 30 | /** 31 | * @{inheritDoc} 32 | */ 33 | public function render() { ?> 34 | 43 | options['choices'] ) && ! empty( $this->options['choices'] ) ) 25 | $this->json_data['choices'] = $this->options['choices']; 26 | 27 | return $this; 28 | } 29 | 30 | /** 31 | * @{inheritDoc} 32 | */ 33 | public function render() { ?> 34 | 44 | 19 | 24 | 25 | children as $child ) { 26 | if ( $child instanceof Custom_Field ) { 27 | $child->render(); 28 | } 29 | } 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function prepare( Custom_Form_Element $parent ) { 36 | $this->parent = $parent; 37 | $this->name = $this->get_name(); 38 | $this->value = array(); 39 | 40 | foreach ( $this->children as $child ) { 41 | $child->prepare( $this ); 42 | } 43 | 44 | return $this; 45 | } 46 | 47 | public function get_posted_value() { 48 | return array( 49 | 'children' => $this->children 50 | ); 51 | } 52 | 53 | public function get_stored_value() { 54 | $value = array(); 55 | 56 | foreach ( $this->children as $child ) { 57 | if ( $child instanceof Custom_Field ) { 58 | $value[ $child->get_id() ] = $child->get_stored_value(); 59 | } 60 | } 61 | 62 | return $value; 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function enqueue_scripts() { 69 | wp_enqueue_script( 'ob-fieldset', OMNI_BUILDER_URI . '/Resources/js/Field/Fieldset.js', array( 'ob-field' ), '0.1', true ); 70 | parent::enqueue_scripts(); 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /Custom_Taxonomy.php: -------------------------------------------------------------------------------- 1 | name = \Inflector::underscore( $name ); 39 | $this->plural = \Inflector::titleize($this->name); 40 | $this->singular = \Inflector::singularize($this->plural); 41 | $this->post_type = $post_type; 42 | $this->args = $args; 43 | 44 | if ( ! isset( $this->args['labels'] ) ) { 45 | $this->args['labels'] = array( 46 | 'name' => __( $this->plural ), 47 | 'singular_name' => __( $this->singular ), 48 | 'search_items' => __( 'Search ' . $this->plural ), 49 | 'all_items' => __( 'All ' . $this->plural ), 50 | 'parent_item' => __( 'Parent ' . $this->singular ), 51 | 'parent_item_colon' => __( 'Parent ' . $this->singular ), 52 | 'edit_item' => __( 'Edit ' . $this->singular ), 53 | 'update_item' => __( 'Update ' . $this->singular ), 54 | 'add_new_item' => __( 'Add New ' . $this->singular ), 55 | 'new_item_name' => __( 'New ' . $this->singular . ' Name' ), 56 | 'menu_name' => __( $this->plural ) 57 | ); 58 | } 59 | 60 | $this->subscribe(); 61 | } 62 | 63 | /** 64 | * @return void 65 | */ 66 | public function register_taxonomy() { 67 | register_taxonomy( 68 | $this->name, 69 | $this->post_type->get_name(), 70 | $this->args 71 | ); 72 | } 73 | 74 | /** 75 | * @return void 76 | */ 77 | protected function subscribe() { 78 | add_action( 'init', array( &$this, 'register_taxonomy' ) ); 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /Field/Image.php: -------------------------------------------------------------------------------- 1 | 23 | 46 | options['preview_size'] ) && ! empty( $this->options['preview_size'] ) ) { 56 | $this->json_data['preview_size'] = $this->options['preview_size']; 57 | } 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function enqueue_scripts() { 64 | wp_enqueue_style( 'thickbox' ); 65 | wp_enqueue_script( 'ob-field-image', OMNI_BUILDER_URI . '/Resources/js/Field/Image.js', array( 'ob-field', 'media-upload', 'thickbox' ), '0.1', true ); 66 | parent::enqueue_scripts(); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /core-post-type.php: -------------------------------------------------------------------------------- 1 | name = $name; 32 | $this->json_data = array(); 33 | $this->screen = null; 34 | $this->show_when = is_callable( $show_when ) ? $show_when : '__return_true'; 35 | 36 | foreach ( $fields as $field ) { 37 | if ( $field instanceof Custom_Meta_Box ) { 38 | $field->set_parent( $this ); 39 | $this->json_data[ $field->get_id() ] = $field->get_json_data(); 40 | } 41 | } 42 | 43 | $this->subscribe(); 44 | } 45 | 46 | /** 47 | * Get Post type name. 48 | * 49 | * @return string 50 | */ 51 | public function get_name() { 52 | return $this->name; 53 | } 54 | 55 | /** 56 | * Get singular Post type name. 57 | * 58 | * @return string 59 | */ 60 | public function get_singular() { 61 | return $this->name; 62 | } 63 | 64 | /** 65 | * Get plural Post type name. 66 | * 67 | * @return string 68 | */ 69 | public function get_plural() { 70 | return $this->name; 71 | } 72 | 73 | /** 74 | * Show on current screen? 75 | * 76 | * @return bool 77 | */ 78 | public function show_on_current_screen() { 79 | if ( $this->show_on_current_screen ) { 80 | return $this->show_on_current_screen; 81 | } 82 | 83 | $current_screen = get_current_screen(); 84 | 85 | if ( is_admin() && $this->name === $current_screen->post_type && true === call_user_func( $this->show_when, $this ) ) { 86 | return $this->show_on_current_screen = true; 87 | } 88 | 89 | return $this->show_on_current_screen = false; 90 | } 91 | 92 | /** 93 | * Register post type 94 | * 95 | * @return void 96 | */ 97 | public function register_post_type() { 98 | return; 99 | } 100 | 101 | /** 102 | * Subscribe to hooks 103 | * 104 | * @return void 105 | */ 106 | protected function subscribe() { 107 | add_action( 'admin_enqueue_scripts', array( &$this, 'localize' ) ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Field/map.php: -------------------------------------------------------------------------------- 1 | 38 | 62 | options['mapbox_public_token'] ) && ! empty( $this->options['mapbox_public_token'] ) ) { 74 | $this->json_data['mapbox_public_token'] = $this->options['mapbox_public_token']; 75 | } 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * Load scripts and styles 82 | */ 83 | public function enqueue_scripts() { 84 | wp_enqueue_script( 'ob-mapbox', 'https://api.mapbox.com/mapbox.js/v2.2.3/mapbox.js', array(), '', true ); 85 | wp_enqueue_style( 'ob-metabox', 'https://api.mapbox.com/mapbox.js/v2.2.3/mapbox.css' ); 86 | wp_enqueue_script( 'ob-field-map', OMNI_BUILDER_URI . '/Resources/js/Field/map.js', array( 'ob-field', 'ob-metabox' ), '0.1', true ); 87 | parent::enqueue_scripts(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Resources/js/Field/Fieldset.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress global variable 3 | * @type {Object} 4 | */ 5 | window.wp = window.wp || {}; 6 | 7 | /** 8 | * @param {Object} exports WordPress global object `wp` 9 | * @param {Object} $ jQuery object `jQuery` 10 | */ 11 | ( function( exports, $ ) { 12 | 13 | /** 14 | * Load OmniBuilder API 15 | * @module OB 16 | * @type {Object} 17 | */ 18 | var api = exports.OB || {}; 19 | 20 | /** 21 | * Fieldset model 22 | * @class Fieldset 23 | * @extends {api.Field} 24 | */ 25 | api.Fieldset = api.Field.extend( { 26 | /** 27 | * Default options 28 | * @memberOf Fieldset 29 | * @type {Object} 30 | * @extends {Field.prototype.defaults} 31 | */ 32 | defaults: _.extend( {}, api.Field.prototype.defaults, { 33 | 'fields': {}, 34 | 'type': 'fieldset' 35 | } ), 36 | /** 37 | * Initialize Fieldset model 38 | * @memberOf Fieldset 39 | */ 40 | initialize: function() { 41 | this.add_fields( this.get( 'fields' ) ); 42 | }, 43 | /** 44 | * Add fields to Fieldset 45 | * @memberOf Fieldset 46 | * @param {Object} data Fields informations 47 | * @return {Fieldset} `this` 48 | */ 49 | add_fields: function( data ) { 50 | this.set( 'fields', api.FieldConstructor( data, { parent: this } ) ); 51 | 52 | return this; 53 | }, 54 | /** 55 | * Return Field with given key as Field ID. This function does not go deeper of first childs. 56 | * @memberOf Fieldset 57 | * @param {String} key Field ID 58 | * @return {Object|Boolean} Return false if Field with that Field ID does not exist. 59 | */ 60 | get_field: function( key ) { 61 | if ( this.get( 'fields' )[key] != null ) return this.get( 'fields' )[key]; 62 | 63 | return false; 64 | }, 65 | /** 66 | * @memberOf Fieldset 67 | * @inheritDoc 68 | */ 69 | set_settings: function( data ) { 70 | for ( key in data ) { 71 | var field = this.get_field( key ) 72 | if ( field != false ) 73 | field.set_settings( data[key] ); 74 | } 75 | 76 | return this; 77 | }, 78 | /** 79 | * @memberOf Fieldset 80 | * @inheritDoc 81 | */ 82 | render: function() { 83 | if ( ! this.view ) { 84 | api.FieldViewConstructor( { model: this } ); 85 | this.listenTo( this.view, 'render', this.render_fields ); 86 | } 87 | 88 | this.trigger( 'render' ); 89 | 90 | return this; 91 | }, 92 | /** 93 | * Render Fieldset fields 94 | * @memberOf Fieldset 95 | * @return {Fieldset} `this` 96 | */ 97 | render_fields: function() { 98 | var fields = this.get( 'fields' ); 99 | for ( key in fields ) 100 | fields[key].render(); 101 | 102 | return this; 103 | }, 104 | /** 105 | * @memberOf Fieldset 106 | * @inheritDoc 107 | */ 108 | generate_names: function() { 109 | api.Field.prototype.generate_names.apply( this ); 110 | 111 | var fields = this.get( 'fields' ); 112 | for ( key in fields ) 113 | fields[key].generate_names(); 114 | 115 | return this; 116 | } 117 | } ); 118 | 119 | /** 120 | * Fieldset view 121 | * @class Fieldset_View 122 | * @extends {Field_View} 123 | */ 124 | api.Fieldset_View = api.Field_View.extend( { 125 | /** 126 | * @memberOf Fieldset_View 127 | * @inheritDoc 128 | */ 129 | className: 'ob-field ob-fieldset', 130 | /** 131 | * @memberOf Fieldset_View 132 | * @inheritDoc 133 | */ 134 | template: wp.template( 'ob-fieldset' ) 135 | } ); 136 | 137 | /** 138 | * Add Fieldset to OmniBuilder 139 | */ 140 | api.add( 'fieldset', { 141 | model: api.Fieldset, 142 | view: api.Fieldset_View 143 | } ); 144 | 145 | /** 146 | * Extend OmniBuilder API 147 | */ 148 | _.extend( exports.OB, api ); 149 | 150 | } ) ( wp, jQuery ) -------------------------------------------------------------------------------- /Custom_Meta_Box.php: -------------------------------------------------------------------------------- 1 | id = $id; 20 | $this->name = $id; 21 | $this->title = $title; 22 | $this->parent = $parent; 23 | $this->children = array(); 24 | 25 | foreach ( $children as $child ) { 26 | if ( $child instanceof Custom_Field ) { 27 | $child->prepare( $this ); 28 | $this->children[$child->get_id()] = $child; 29 | } 30 | } 31 | 32 | $this->subscribe(); 33 | } 34 | 35 | /** 36 | * @return void 37 | */ 38 | public function set_meta_box() { 39 | if ( true === $this->parent->show_on_current_screen() ) { 40 | add_meta_box( $this->id, $this->title, array( &$this, 'populate_meta_box' ), $this->parent->get_name(), 'normal' ); 41 | } 42 | } 43 | 44 | /** 45 | * @return void 46 | */ 47 | public function populate_meta_box() { 48 | ?> 49 |
50 |
51 | 54 | 55 | children as $child ) { 56 | if ( $child instanceof Custom_Field ) { 57 | $child->render(); 58 | } 59 | } ?> 60 | 61 |
62 | cap->edit_post, $post_id ) ) { 75 | return null; 76 | } 77 | 78 | $this->save( $post_id, $this->children ); 79 | } 80 | 81 | /** 82 | * @param int $post_id 83 | * @param array $fields 84 | */ 85 | public function save( $post_id, $fields ) { 86 | foreach ( $fields as $field ) { 87 | $value = $field->get_posted_value(); 88 | if ( $field->is_posted() ) { 89 | if ( is_array( $value ) && isset( $value['children'] ) ) { 90 | $this->save( $post_id, $value['children'] ); 91 | } else { 92 | update_post_meta( $post_id, '_' . $field->get_name(), $value ); 93 | } 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * @return array 100 | */ 101 | public function get_stored_value() { 102 | $value = array(); 103 | foreach ( $this->children as $child ) { 104 | if ( $child instanceof Custom_Field ) { 105 | $value[ $child->get_id() ] = $child->get_stored_value(); 106 | } 107 | } 108 | 109 | return $value; 110 | } 111 | 112 | /** 113 | * @return $this 114 | */ 115 | private function subscribe() { 116 | add_action( 'admin_enqueue_scripts', array( &$this, 'enqueue_scripts' ) ); 117 | add_action( 'add_meta_boxes', array( &$this, 'set_meta_box' ) ); 118 | add_action( 'save_post', array( &$this, 'save_meta_box_data' ) ); 119 | 120 | return $this; 121 | } 122 | 123 | /** 124 | * {@inheritdoc} 125 | */ 126 | public function set_json_data() { 127 | parent::set_json_data(); 128 | $this->json_data['title'] = $this->title; 129 | $this->json_data['settings'] = $this->get_stored_value(); 130 | 131 | return $this; 132 | } 133 | 134 | /** 135 | * {@inheritdoc} 136 | */ 137 | public function enqueue_scripts() { 138 | wp_enqueue_script( 'ob-metabox', OMNI_BUILDER_URI . '/Resources/js/Metabox.js', array( 'ob' ), '0.1', true ); 139 | parent::enqueue_scripts(); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /Custom_Form_Element.php: -------------------------------------------------------------------------------- 1 | id; 42 | } 43 | 44 | /** 45 | * @return Custom_Form_Element|Custom_Post_Type 46 | */ 47 | public function get_parent() { 48 | return $this->parent; 49 | } 50 | 51 | /** 52 | * @param Custom_Form_Element|Custom_Post_Type 53 | * @return $this 54 | */ 55 | public function set_parent( $parent ) { 56 | $this->parent = $parent; 57 | 58 | return $this; 59 | } 60 | 61 | /** 62 | * @return array 63 | */ 64 | public function get_children() { 65 | return $this->children; 66 | } 67 | 68 | /** 69 | * @param array $children 70 | * @return $this 71 | */ 72 | public function set_children( array $children ) { 73 | $this->children = $children; 74 | 75 | return $this; 76 | } 77 | 78 | /** 79 | * @return string 80 | */ 81 | public function get_name() { 82 | if ( null !== $this->name ) { 83 | return $this->name; 84 | } 85 | 86 | if ( $this->parent instanceof Custom_Post_Type ) { 87 | return $this->name = '_' . $this->id; 88 | } 89 | 90 | return $this->name = $this->parent->get_name() . '_' . $this->id; 91 | } 92 | 93 | /** 94 | * @param string $name 95 | * @return $this 96 | */ 97 | public function set_name( $name ) { 98 | $this->name = $name; 99 | 100 | return $this; 101 | } 102 | 103 | /** 104 | * @return string 105 | */ 106 | public function get_name_attribute() { 107 | if ( null !== $this->name_attribute ) { 108 | return $this->name_attribute; 109 | } 110 | 111 | if ( $this->parent instanceof Custom_Post_Type ) { 112 | return $this->name_attribute = '_' . $this->id; 113 | } 114 | 115 | return $this->name_attribute = $this->parent->get_name_attribute() . '_' . $this->id; 116 | } 117 | 118 | /** 119 | * @param string $name_attribute 120 | * @return $this 121 | */ 122 | public function set_name_attribute( $name_attribute ) { 123 | $this->name_attribute = $name_attribute; 124 | 125 | return $this; 126 | } 127 | 128 | /** 129 | * @return string 130 | */ 131 | public function get_json_data() { 132 | if ( ! $this->json_data ) { 133 | $this->set_json_data(); 134 | } 135 | 136 | return $this->json_data; 137 | } 138 | 139 | /** 140 | * @return $this 141 | */ 142 | public function set_json_data() { 143 | $this->json_data = array(); 144 | $this->json_data['id'] = $this->get_id(); 145 | 146 | $children = $this->get_children(); 147 | if ( ! empty( $children ) ) { 148 | $fields = array(); 149 | foreach ( $children as $child ) { 150 | if ( $child instanceof Custom_Field ) { 151 | $fields[$child->get_id()] = $child->get_json_data(); 152 | } 153 | } 154 | $this->json_data['fields'] = $fields; 155 | } 156 | 157 | return $this; 158 | } 159 | 160 | /** 161 | * @return void 162 | */ 163 | public function enqueue_scripts() { 164 | if ( $this->parent->show_on_current_screen() ) { 165 | $children = $this->get_children(); 166 | if ( ! empty( $children ) ) { 167 | foreach ( $children as $child ) { 168 | if ( $child instanceof Custom_Field ) { 169 | $child->enqueue_scripts(); 170 | } 171 | } 172 | } 173 | } 174 | } 175 | 176 | /** 177 | * @return bool 178 | */ 179 | public function show_on_current_screen() { 180 | if ( $this->parent ) { 181 | return $this->parent->show_on_current_screen(); 182 | } 183 | 184 | return false; 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /Custom_Post_Type.php: -------------------------------------------------------------------------------- 1 | name = \Inflector::underscore( \Inflector::singularize($name) ); 37 | $this->singular = \Inflector::titleize($name); 38 | $this->plural = \Inflector::pluralize($this->singular); 39 | $this->json_data = array(); 40 | $this->screen = null; 41 | $this->args = $args; 42 | 43 | foreach ( $fields as $field ) { 44 | if ( $field instanceof Custom_Meta_Box ) { 45 | $field->set_parent( $this ); 46 | $this->json_data[ $field->get_id() ] = $field->get_json_data(); 47 | } 48 | } 49 | 50 | if ( ! isset( $this->args['public'] ) ) { 51 | $this->args['public'] = $public; 52 | } 53 | 54 | if ( ! isset( $this->args['labels'] ) ) { 55 | $this->args['labels'] = array( 56 | 'name' => __( $this->singular ), 57 | 'singular_name' => __( $this->singular ), 58 | 'menu_name' => __( $this->plural ), 59 | 'name_admin_bar' => __( $this->singular ), 60 | 'add_new' => __( 'Add New' ), 61 | 'add_new_item' => __( 'Add New ' . $this->singular ), 62 | 'edit_item' => __( 'Edit ' . $this->singular ), 63 | 'view_item' => __( 'View ' . $this->singular ), 64 | 'all_items' => __( 'All ' . $this->plural ), 65 | 'search_items' => __( 'Search ' . $this->plural ), 66 | 'parent_item_colon' => __( 'Parent ' . $this->plural . ':' ), 67 | 'not_found' => __( 'No ' . strtolower( $this->plural ) . ' found.' ), 68 | 'not_found_in_trash' => __( 'No ' . strtolower( $this->plural ) . ' found in trash.' ) 69 | ); 70 | } 71 | 72 | $this->subscribe(); 73 | } 74 | 75 | /** 76 | * @param string $name 77 | * @param array $args 78 | * @return $this 79 | */ 80 | public function add_taxonomy( $name, array $args = array() ) { 81 | new Custom_Taxonomy( $name, $this ); 82 | 83 | return $this; 84 | } 85 | 86 | /** 87 | * @param string $id 88 | * @param string $title 89 | * @param array $fields 90 | * @return $this 91 | */ 92 | public function add_meta_box( $id, $title, array $fields = array() ) { 93 | new Custom_Meta_Box( $id, $title, $fields, $this ); 94 | 95 | return $this; 96 | } 97 | 98 | /** 99 | * @return string 100 | */ 101 | public function get_name() { 102 | return $this->name; 103 | } 104 | 105 | /** 106 | * @return string 107 | */ 108 | public function get_singular() { 109 | return $this->singular; 110 | } 111 | 112 | /** 113 | * @return string 114 | */ 115 | public function get_plural() { 116 | return $this->plural; 117 | } 118 | 119 | /** 120 | * @return void 121 | */ 122 | public function localize() { 123 | if ( $this->show_on_current_screen() ) { 124 | wp_localize_script( 'ob', 'ob', $this->json_data ); 125 | } 126 | } 127 | 128 | /** 129 | * @return bool 130 | */ 131 | public function show_on_current_screen() { 132 | if ( $this->show_on_current_screen ) 133 | return $this->show_on_current_screen; 134 | 135 | $current_screen = get_current_screen(); 136 | 137 | if ( in_array( $current_screen->base, array( 'post' ) ) && $current_screen->post_type == $this->name ) { 138 | return $this->show_on_current_screen = true; 139 | } 140 | 141 | return $this->show_on_current_screen = false; 142 | } 143 | 144 | /** 145 | * @return void 146 | */ 147 | public function register_post_type() { 148 | if ( ! post_type_exists( $this->name ) ) { 149 | register_post_type( $this->name, $this->args ); 150 | } 151 | } 152 | 153 | /** 154 | * @return void 155 | */ 156 | protected function subscribe() { 157 | add_action( 'init', array( &$this, 'register_post_type' ) ); 158 | add_action( 'admin_enqueue_scripts', array( &$this, 'localize' ) ); 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /Custom_Field.php: -------------------------------------------------------------------------------- 1 | id = $id; 34 | $this->parent = null; 35 | $this->children = array(); 36 | $this->name = null; 37 | $this->value = null; 38 | $this->set_options( $options ); 39 | 40 | foreach ( $children as $child ) { 41 | $this->children[ $child->get_id() ] = $child; 42 | } 43 | } 44 | 45 | /** 46 | * @return mixed 47 | */ 48 | abstract public function render(); 49 | 50 | /** 51 | * @param Custom_Form_Element $parent 52 | * @return $this 53 | */ 54 | public function prepare( Custom_Form_Element $parent ) { 55 | $this->parent = $parent; 56 | $this->name = $this->get_name(); 57 | $this->value = $this->get_stored_value(); 58 | 59 | foreach ( $this->children as $child ) { 60 | $child->prepare( $this ); 61 | } 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * @return mixed 68 | */ 69 | public function get_value() { 70 | return $value; 71 | } 72 | 73 | /** 74 | * @param mixed $value 75 | * @return $this 76 | */ 77 | public function set_value( $value ) { 78 | $this->value = $value; 79 | 80 | return $this; 81 | } 82 | 83 | /** 84 | * @return array 85 | */ 86 | public function get_options() { 87 | return $this->options; 88 | } 89 | 90 | /** 91 | * @param array 92 | * @return $this 93 | */ 94 | public function set_options( array $options ) { 95 | $this->options = $options; 96 | $this->validate_options(); 97 | 98 | return $this; 99 | } 100 | 101 | /** 102 | * Validate options 103 | * @return $this 104 | */ 105 | private function validate_options() { 106 | if ( ! is_array( $this->options ) ) 107 | $this->options = array(); 108 | elseif ( isset( $this->options['attributes'] ) ) { 109 | if ( ! is_array( $this->options['attributes'] ) ) 110 | $this->options['attributes'] = array(); 111 | else { 112 | foreach ( $this->options['attributes'] as $attribute => $value ) { 113 | if ( ! in_array( strtolower( $attribute ), $this->allowed_attributes ) && substr( strtolower( $attribute ), 0, 5 ) != 'data-' ) continue; 114 | $this->options['attributes'][$attribute] = esc_attr( $value ); 115 | } 116 | } 117 | } 118 | 119 | return $this; 120 | } 121 | 122 | /** 123 | * @return array|null 124 | */ 125 | public function get_stored_value() { 126 | $action = ( isset( $_GET['action'] ) )? $_GET['action'] : null ; 127 | $post_id = ( isset( $_GET['post'] ) )? $_GET['post'] : null ; 128 | $is_editing = ( strstr($_SERVER['REQUEST_URI'], 'wp-admin/post.php') && $action == 'edit' && $post_id ); 129 | 130 | if ( $is_editing && get_post( $post_id ) ) { 131 | $post = get_post( sanitize_text_field( $post_id ) ); 132 | return get_post_meta( $post_id, '_' . $this->get_name(), true ); 133 | } 134 | 135 | return null; 136 | } 137 | 138 | /** 139 | * @return boolean 140 | */ 141 | public function is_posted() { 142 | return ( isset( $_POST[ '_' . $this->get_name() ] ) ) ? true : false; 143 | } 144 | 145 | /** 146 | * @return string|null 147 | */ 148 | public function get_posted_value() { 149 | return $this->is_posted() ? (string) $_POST[ '_' . $this->get_name() ] : null; 150 | } 151 | 152 | /** 153 | * @{inheritDoc} 154 | */ 155 | public function set_json_data() { 156 | parent::set_json_data(); 157 | 158 | $this->json_data['type'] = $this->type; 159 | 160 | if ( isset( $this->options['label'] ) && ! empty( $this->options['label'] ) ) 161 | $this->json_data['label'] = $this->options['label']; 162 | 163 | if ( isset( $this->options['description'] ) && ! empty( $this->options['description'] ) ) 164 | $this->json_data['description'] = $this->options['description']; 165 | 166 | if ( isset( $this->options['default_value'] ) && ! empty( $this->options['default_value'] ) ) 167 | $this->json_data['default_value'] = $this->options['default_value']; 168 | 169 | if ( isset( $this->options['attributes'] ) && ! empty( $this->options['attributes'] ) ) 170 | $this->json_data['attributes'] = $this->options['attributes']; 171 | 172 | return $this; 173 | } 174 | 175 | /** 176 | * @{inheritDoc} 177 | */ 178 | public function enqueue_scripts() { 179 | wp_enqueue_script( 'ob-field', OMNI_BUILDER_URI . '/Resources/js/Field.js', array( 'ob-metabox' ), '0.1', true ); 180 | parent::enqueue_scripts(); 181 | } 182 | 183 | } 184 | -------------------------------------------------------------------------------- /Resources/js/Metabox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress global object 3 | * @type {Object} 4 | */ 5 | window.wp = window.wp || {}; 6 | 7 | /** 8 | * @param {Object} exports WordPress global object `wp` 9 | * @param {Object} $ jQuery object `jQuery` 10 | */ 11 | ( function( exports, $ ) { 12 | 13 | /** 14 | * Load OmniBuilder API 15 | * @module OB 16 | * @type {Object} 17 | */ 18 | var api = exports.OB || {}; 19 | 20 | /** 21 | * Metabox model 22 | * @class Metabox 23 | * @extends {Backbone.Model} 24 | */ 25 | api.Metabox = Backbone.Model.extend( { 26 | /** 27 | * Default options 28 | * @memberOf Metabox 29 | * @type {Object} 30 | */ 31 | defaults: { 32 | 'title': '', 33 | 'description': '', 34 | 'fields': {}, 35 | 'settings': {}, 36 | 'rendered': false 37 | }, 38 | /** 39 | * Initialize Metabox 40 | * @memberOf Metabox 41 | */ 42 | initialize: function() { 43 | this.add_fields( this.get( 'fields' ) ); 44 | this.set_settings( this.get( 'settings' ) ); 45 | }, 46 | /** 47 | * Add fields to Metabox 48 | * @memberOf Metabox 49 | * @param {Object} data Fields informations 50 | * @return {Metabox} `this` 51 | */ 52 | add_fields: function( data ) { 53 | this.set( 'fields', api.FieldConstructor( data, { parent: this } ) ); 54 | 55 | return this; 56 | }, 57 | /** 58 | * Return Field with given key as Field ID. This function does not go deeper of first childs. 59 | * @memberOf Metabox 60 | * @param {String} key Field ID 61 | * @return {Object|Boolean} Return false if Field with that Field ID does not exist. 62 | */ 63 | get_field: function( key ) { 64 | if ( this.get( 'fields' )[key] != null ) return this.get( 'fields' )[key]; 65 | 66 | return false; 67 | }, 68 | /** 69 | * Set settings to fields 70 | * @memberOf Metabox 71 | * @param {Object} data Settings informations 72 | * @return {Metabox} `this` 73 | */ 74 | set_settings: function( data ) { 75 | for ( key in data ) { 76 | var field = this.get_field( key ) 77 | if ( field != false ) 78 | field.set_settings( data[key] ); 79 | } 80 | 81 | return this; 82 | }, 83 | /** 84 | * Render Metabox 85 | * @memberOf Metabox 86 | * @return {Metabox} `this` 87 | */ 88 | render: function() { 89 | this.view = new api.Metabox_View( { model: this, el: $( '#' + this.id + ' .ob-metabox-wrapper' ) } ); 90 | this.listenTo( this.view, 'render', this.render_fields ); 91 | 92 | this.trigger( 'render' ); 93 | 94 | return this; 95 | }, 96 | /** 97 | * Render Fields 98 | * @memberOf Metabox 99 | * @return {Metabox} `this` 100 | */ 101 | render_fields: function() { 102 | var fields = this.get( 'fields' ); 103 | for ( key in fields ) 104 | fields[key].render(); 105 | 106 | return this; 107 | }, 108 | /** 109 | * Generate names on each field 110 | * @memberOf Metabox 111 | * @param {String} key Metabox ID 112 | * @return {Metabox} `this` 113 | */ 114 | generate_names: function( key ) { 115 | this.set( 'name', '_' + key ); 116 | 117 | var fields = this.get( 'fields' ); 118 | for ( key in fields ) 119 | fields[key].generate_names(); 120 | 121 | return this; 122 | } 123 | } ); 124 | 125 | /** 126 | * Metabox view 127 | * @class Metabox_View 128 | * @extends {Backbone.View} 129 | */ 130 | api.Metabox_View = Backbone.View.extend( { 131 | /** 132 | * DOM Element class attribute 133 | * @memberOf Metabox_View 134 | * @type {String} 135 | */ 136 | className: 'ob-metabox', 137 | /** 138 | * Metabox template 139 | * @memberOf Metabox_View 140 | * @type {Object} 141 | */ 142 | template: wp.template( 'ob-metabox' ), 143 | /** 144 | * Listen to some model events 145 | * @memberOf Metabox_View 146 | */ 147 | initialize: function() { 148 | this.listenTo( this.model, 'render', this.render ); 149 | }, 150 | /** 151 | * Metabox render 152 | * @memberOf Metabox_View 153 | * @return {Metabox_View} `this` 154 | */ 155 | render: function() { 156 | api.render( function() { 157 | this.$el.html( this.template( { description: this.model.get( 'description' ) } ) ); 158 | this.model.set( 'rendered', true ); 159 | this.trigger( 'render' ); 160 | }.bind( this ) ); 161 | 162 | return this; 163 | }, 164 | /** 165 | * Returns fields element 166 | * @memberOf Metabox_View 167 | * @return {Element} 168 | */ 169 | getFieldsEl: function() { 170 | if ( this.$fields ) return this.$fields; 171 | 172 | var fields = $( '.fields', this.$el ); 173 | if ( ! fields.length ) { 174 | this.$el.append( '
' ); 175 | fields = $( '.fields', this.$el ); 176 | } 177 | 178 | this.$fields = fields; 179 | 180 | return this.$fields; 181 | } 182 | } ); 183 | 184 | /** 185 | * Extend OmniBuilder API 186 | */ 187 | _.extend( exports.OB, api ); 188 | 189 | } ) ( wp, jQuery ) -------------------------------------------------------------------------------- /Resources/js/Field/WP_Editor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress global variable 3 | * @type {Object} 4 | */ 5 | window.wp = window.wp || {}; 6 | 7 | /** 8 | * @param {Object} exports WordPress global object `wp` 9 | * @param {Object} $ jQuery object `jQuery` 10 | */ 11 | ( function( exports, $ ) { 12 | 13 | /** 14 | * Load OmniBuilder API 15 | * @module OB 16 | * @type {Object} 17 | */ 18 | var api = exports.OB || {}; 19 | 20 | /** 21 | * WP Editor Field model 22 | * @class Field_WP_Editor 23 | * @extends {Field} 24 | */ 25 | api.Field_WP_Editor = api.Field.extend( { 26 | /** 27 | * Default options 28 | * @memberOf Field_WP_Editor 29 | * @type {Object} 30 | * @extends {Field.prototype.defaults} 31 | */ 32 | defaults: _.extend( {}, api.Field.prototype.defaults, { 33 | 'type': 'wp_editor' 34 | } ), 35 | initialize: function() { 36 | api.Field.prototype.initialize.call( this ); 37 | 38 | var parent = this.get( 'parent' ); 39 | 40 | while( parent && parent.is_collection_child && parent.is_collection_child() ) { 41 | if ( _.indexOf( [ 'collection-fieldset', 'collection-block' ], parent.get( 'type' ) ) >= 0 ) { 42 | this.listenTo( parent, 'before_move', this.collect_values ); 43 | this.listenTo( parent, 'after_move', this.update_value ); 44 | } 45 | 46 | parent = parent.get( 'parent' ); 47 | } 48 | }, 49 | update_value: function() { 50 | this.view.trigger( 'update_value' ); 51 | } 52 | } ); 53 | 54 | /** 55 | * WP Editor Field view 56 | * @class Field_WP_Editor_View 57 | * @extends {Field_View} 58 | */ 59 | api.Field_WP_Editor_View = api.Field_View.extend( { 60 | initialize: function() { 61 | api.Field_View.prototype.initialize.call( this ); 62 | 63 | this.on( 'appended', this.initEditor ); 64 | this.on( 'update_value', this.update_value ); 65 | }, 66 | initEditor: function() { 67 | api.render( this._initEditor.bind( this ) ); 68 | }, 69 | _initEditor: function() { 70 | var name = this.model.get( 'name' ); 71 | var $wrap = $( '.wp-editor-wrap', this.$el ).attr( 'id', 'wp-' + name + '-wrap' ); 72 | var $tools = $( '.wp-editor-tools', $wrap ).attr( 'id', 'wp-' + name + '-editor-tools' ); 73 | var $media_buttons = $( '.wp-media-buttons', $tools ).attr( 'id', 'wp-' + name + '-media-buttons' ); 74 | $( '[data-editor]', $wrap ).attr( 'data-editor', name ); 75 | $( '[data-wp-editor-id]', $wrap ).attr( 'data-wp-editor-id', name ); 76 | var $switch_html = $( '.switch-html', $tools ).attr( 'id', name + '-html' ); 77 | var $switch_tmce = $( '.switch-tmce', $tools ).attr( 'id', name + '-tmce' ); 78 | var $editor_container = $( '.wp-editor-container', $wrap ).attr( 'id', 'wp-' + name + '-editor-container' ); 79 | var $quicktags_toolbar = $( '.quicktags-toolbar', $wrap ).attr( 'id', 'qt_' + name + '_toolbar' ); 80 | var $textarea = $( '[name="ob-field-wp_editor"]', $editor_container ).attr( { 81 | 'name': name, 82 | 'id': name 83 | } ); 84 | var mode = $wrap.hasClass( 'tmce-active' ) ? 'tmce' : 'html'; 85 | 86 | if ( typeof tinymce !== 'undefined' ) { 87 | var mceInit; 88 | 89 | mceInit = tinymce.extend( {}, tinyMCEPreInit.mceInit[ 'ob-field-wp_editor' ] ); 90 | mceInit.body_class = mceInit.body_class.replace( 'ob-field-wp_editor', name ); 91 | mceInit.selector = '#' + name; 92 | 93 | tinyMCEPreInit.mceInit[ name ] = mceInit; 94 | 95 | if ( mode === 'tmce' ) { 96 | try { 97 | tinymce.init( mceInit ); 98 | } catch ( e ) {} 99 | } 100 | } 101 | 102 | if ( typeof quicktags !== 'undefined' ) { 103 | var qtInit; 104 | if ( tinyMCEPreInit.qtInit[ name ] == undefined ) { 105 | qtInit = $.extend( {}, tinyMCEPreInit.qtInit[ 'ob-field-wp_editor' ] ); 106 | qtInit.id = name; 107 | tinyMCEPreInit.qtInit[ name ] = qtInit; 108 | 109 | try { 110 | quicktags( qtInit ); 111 | QTags._buttonsInit(); 112 | 113 | if ( ! window.wpActiveEditor ) { 114 | window.wpActiveEditor = name; 115 | } 116 | } catch( e ) {}; 117 | } 118 | else qtInit = tinyMCEPreInit.qtInit[ name ]; 119 | } 120 | 121 | $( $wrap ).on( 'click.wp-editor', function() { 122 | if ( this.id ) { 123 | window.wpActiveEditor = this.id.slice( 3, -5 ); 124 | } 125 | } ); 126 | 127 | if ( tinymce.get( name ) !== null ) { 128 | switchEditors.go( name, mode ); 129 | } 130 | }, 131 | get_value: function() { 132 | var name = this.model.get( 'name' ); 133 | 134 | var value; 135 | 136 | if ( $( '#wp-' + name + '-wrap', this.$el ).hasClass( 'tmce-active' ) ) 137 | value = tinymce.get( name ).getContent(); 138 | else 139 | value = $( '#' + name, this.$el ).val(); 140 | 141 | return value; 142 | }, 143 | update_value: function() { 144 | api.render( function() { 145 | var name = this.model.get( 'name' ); 146 | var value = this.model.get( 'setting' ).get( 'value' ); 147 | 148 | if ( $( '#wp-' + name + '-wrap', this.$el ).hasClass( 'tmce-active' ) ) { 149 | if ( tinymce.get( name ) ) { 150 | tinymce.get( name ).remove(); 151 | } 152 | 153 | this._initEditor(); 154 | 155 | if ( tinymce.get( name ) ) { 156 | tinymce.get( name ).setContent( value ); 157 | } 158 | } 159 | else { 160 | $( '#' + name, this.$el ).val( value ); 161 | } 162 | }.bind( this ) ); 163 | } 164 | } ); 165 | 166 | /** 167 | * Add WP Editor Field to OmniBuilder 168 | */ 169 | api.add( 'wp_editor', { 170 | model: api.Field_WP_Editor, 171 | view: api.Field_WP_Editor_View 172 | } ); 173 | 174 | /** 175 | * Extend OmniBuilder API 176 | */ 177 | _.extend( exports.OB, api ); 178 | 179 | } ) ( wp, jQuery ) -------------------------------------------------------------------------------- /Resources/js/Field/Image.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress global variable 3 | * @type {Object} 4 | */ 5 | window.wp = window.wp || {}; 6 | 7 | /** 8 | * @param {Object} exports WordPress global object `wp` 9 | * @param {Object} $ jQuery object `jQuery` 10 | */ 11 | ( function( exports, $ ) { 12 | 13 | /** 14 | * Load OmniBuilder API 15 | * @module OB 16 | * @type {Object} 17 | */ 18 | var api = exports.OB || {}; 19 | 20 | /** 21 | * Image Field model 22 | * @class Field_Image 23 | * @extends {Field} 24 | */ 25 | api.Field_Image = api.Field.extend( { 26 | /** 27 | * Default options 28 | * @memberOf Field_Image 29 | * @type {Object} 30 | * @extends {Field.prototype.defaults} 31 | */ 32 | defaults: _.extend( {}, api.Field.prototype.defaults, { 33 | 'type': 'image', 34 | 'attachment': null 35 | } ), 36 | initialize: function() { 37 | api.Field.prototype.initialize.apply( this ); 38 | 39 | this.listenTo( this.get( 'setting' ), 'change:value', this.updateAttachment ); 40 | }, 41 | updateAttachment: function( model, value ) { 42 | if ( _.isFinite( value ) ) { 43 | this.set( 'attachment', new wp.media.model.Attachment( { id: value } ) ); 44 | this.get( 'attachment' ).fetch().done( this.reRender.bind( this ) ).fail( function() { 45 | this.set( 'attachment', null ); 46 | this.reRender(); 47 | }.bind( this ) ); 48 | } 49 | else { 50 | this.set( 'attachment', null ); 51 | this.reRender(); 52 | } 53 | }, 54 | reRender: function() { 55 | if ( ! this.view ) return; 56 | 57 | this.set( 'rendered', false ); 58 | this.render(); 59 | } 60 | } ); 61 | 62 | /** 63 | * Image Field view 64 | * @class Field_Image_View 65 | * @extends {Field_View} 66 | */ 67 | api.Field_Image_View = api.Field_View.extend( { 68 | /** 69 | * Element class 70 | * @memberOf Field_Image_View 71 | * @type {String} 72 | * @extends {Field_View.prototype.className} 73 | */ 74 | className: api.Field_View.prototype.className + ' ob-field-image', 75 | /** 76 | * WordPress media frame 77 | * @memberOf Field_Image_View 78 | * @type {Object|null} 79 | */ 80 | frame: null, 81 | /** 82 | * Element events 83 | * @memberOf Field_Image_View 84 | * @type {Object} 85 | */ 86 | events: { 87 | 'click div:not(.ob-field) .button': 'openMedia', 88 | 'click div:not(.ob-field) .ob-field-image-remove': 'removeImage' 89 | }, 90 | /** 91 | * Open WordPress media frame 92 | * @param {Event} event 93 | * @memberOf Field_Image_View 94 | */ 95 | openMedia: function( event ) { 96 | event.preventDefault(); 97 | 98 | if ( this.frame == null ) { 99 | 100 | var l10n = _wpMediaViewsL10n; 101 | this.frame = wp.media( { 102 | button: { 103 | text: l10n.select, 104 | close: false 105 | }, 106 | states: [ 107 | new wp.media.controller.Library( { 108 | title: l10n.chooseImage, 109 | library: wp.media.query( { 110 | type: 'image' 111 | } ), 112 | multiple: false, 113 | date: false, 114 | priority: 20 115 | } ) 116 | ] 117 | } ); 118 | 119 | this.frame.on( 'open', this.onOpen, this ); 120 | this.frame.on( 'select', this.onSelect, this ); 121 | } 122 | 123 | this.frame.open(); 124 | }, 125 | /** 126 | * Update attachment object after WordPress media frame is opened 127 | * @memberOf Field_Image_View 128 | */ 129 | onOpen: function() { 130 | var value = this.get_value(); 131 | if ( ! _.isFinite( value ) ) return; 132 | 133 | if ( this.model.get( 'attachment' ) == null || this.model.get( 'attachment' ).get( 'id' ) != value ) { 134 | this.model.set( 'attachment', new wp.media.model.Attachment( { id: value } ) ); 135 | this.model.get( 'attachment' ).fetch().done( this.addToSelection.bind( this ) ); 136 | } 137 | 138 | this.addToSelection(); 139 | }, 140 | /** 141 | * Update setting value on select 142 | * @memberOf Field_Image_View 143 | */ 144 | onSelect: function() { 145 | var attachment = this.frame.state().get( 'selection' ).first(); 146 | this.model.get( 'setting' ).set( 'value', attachment.get( 'id' ) ); 147 | 148 | this.frame.close(); 149 | }, 150 | /** 151 | * Add attachment object to wp media frame selection 152 | * @memberOf Field_Image_View 153 | */ 154 | addToSelection: function() { 155 | if ( this.model.get( 'attachment' ) == null ) return; 156 | 157 | var selection = this.frame.state().get( 'selection' ); 158 | selection.add( [ this.model.get( 'attachment' ) ] ); 159 | }, 160 | /** 161 | * Handle click on remove button 162 | * @param {Event} event 163 | * @memberOf Field_Image_View 164 | */ 165 | removeImage: function( event ) { 166 | event.preventDefault(); 167 | 168 | this.model.get( 'setting' ).set( 'value', '' ); 169 | }, 170 | /** 171 | * Get input element value 172 | * @memberOf Field_Image_View 173 | * @return {String} 174 | */ 175 | get_value: function() { 176 | return $( 'input[name="' + this.model.get( 'name' ) + '"]', this.$el ).val(); 177 | }, 178 | /** 179 | * Set input element value 180 | * @memberOf Field_Image_View 181 | * @return {Field_Image_View} 182 | */ 183 | set_value: function( selection ) { 184 | $( 'input[name="' + this.model.get( 'name' ) + '"]', this.$el ).val( selection.get( 'id' ) ); 185 | 186 | return this; 187 | } 188 | } ); 189 | 190 | /** 191 | * Add Text Field to OmniBuilder 192 | */ 193 | api.add( 'image', { 194 | model: api.Field_Image, 195 | view: api.Field_Image_View 196 | } ); 197 | 198 | /** 199 | * Extend OmniBuilder API 200 | */ 201 | _.extend( exports.OB, api ); 202 | 203 | } ) ( wp, jQuery ) 204 | 205 | -------------------------------------------------------------------------------- /Resources/js/Field/map.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress global variable 3 | * @type {Object} 4 | */ 5 | window.wp = window.wp || {}; 6 | 7 | /** 8 | * @param {Object} exports WordPress global object `wp` 9 | * @param {Object} $ jQuery object `jQuery` 10 | */ 11 | ( function( exports, $ ) { 12 | 13 | /** 14 | * Load OmniBuilder API 15 | * @module OB 16 | * @type {Object} 17 | */ 18 | var api = exports.OB || {}; 19 | 20 | /** 21 | * Map Field model 22 | * @class Field_Map 23 | * @extends {Field} 24 | */ 25 | api.Field_Map = api.Field.extend( { 26 | /** 27 | * Default options 28 | * @memberOf Field_Map 29 | * @type {Object} 30 | * @extends {Field.prototype.defaults} 31 | */ 32 | defaults: _.extend( {}, api.Field.prototype.defaults, { 33 | 'type': 'map', 34 | 'mapbox_public_token': '', 35 | } ), 36 | helpers: { 37 | validateLat: function( lat ) { 38 | if ( _.isFinite( lat ) && lat >= -90 && lat <= 90 ) { 39 | return lat; 40 | } 41 | else { 42 | return 0; 43 | } 44 | }, 45 | validateLng: function( lng ) { 46 | if ( _.isFinite( lng ) && lng >= -180 && lng <= 180 ) { 47 | return lng; 48 | } 49 | else { 50 | return 0; 51 | } 52 | } 53 | }, 54 | initialize: function() { 55 | // Validate attributes before initialization. 56 | var default_value = this.get( 'default_value' ); 57 | var value = this.get( 'value' ); 58 | 59 | if ( _.isObject( default_value ) ) { 60 | default_value['lat'] = this.helpers.validateLat( default_value['lat'] ); 61 | default_value['lng'] = this.helpers.validateLng( default_value['lng'] ); 62 | } else { 63 | default_value = { lat: 0, lng: 0 }; 64 | } 65 | 66 | this.set( 'default_value', default_value ); 67 | 68 | if ( _.isObject( value ) ) { 69 | value['lat'] = this.helpers.validateLat( value['lat'] ); 70 | value['lng'] = this.helpers.validateLng( value['lng'] ); 71 | 72 | this.set( 'value', value ); 73 | } 74 | 75 | api.Field.prototype.initialize.call( this ); 76 | } 77 | } ); 78 | 79 | /** 80 | * Map Field view 81 | * @class Field_Map_View 82 | * @extends {Field_View} 83 | */ 84 | api.Field_Map_View = api.Field_View.extend( { 85 | /** 86 | * Element class 87 | * @memberOf Field_Map_View 88 | * @type {String} 89 | * @extends {Field_View.prototype.className} 90 | */ 91 | className: api.Field_View.prototype.className + ' ob-field-map', 92 | /** 93 | * Map 94 | * @memberOf Field_Map_View 95 | * @type {Object} 96 | */ 97 | map: null, 98 | /** 99 | * Marker 100 | * @memberOf Field_Map_View 101 | * @type {Object} 102 | */ 103 | marker: null, 104 | /** 105 | * Element events 106 | * @memberOf Field_Map_View 107 | * @type {Object} 108 | */ 109 | events: { 110 | 'change div:not(.ob-field) input.latitude': 'updateMarker', 111 | 'change div:not(.ob-field) input.longitude': 'updateMarker' 112 | }, 113 | initialize: function() { 114 | api.Field_View.prototype.initialize.call( this ); 115 | 116 | this.on( 'appended', this.initMapbox ); 117 | }, 118 | parseData: function( data ) { 119 | data['subfields'] = [ 120 | { 121 | name: data.name + '[lat]', 122 | label: 'Latitude', 123 | value: data.value['lat'], 124 | classes: 'latitude' 125 | }, 126 | { 127 | name: data.name + '[lng]', 128 | label: 'Longitude', 129 | value: data.value['lng'], 130 | classes: 'longitude' 131 | } 132 | ]; 133 | 134 | return data; 135 | }, 136 | setAccessToken: function() { 137 | var accessToken = this.model.get( 'mapbox_public_token' ); 138 | if ( accessToken.length ) { 139 | L.mapbox.accessToken = accessToken; 140 | return true; 141 | } 142 | 143 | return false; 144 | }, 145 | initMapbox: function() { 146 | if ( ! this.setAccessToken() ) { 147 | return; 148 | } 149 | 150 | var data = this.getData(); 151 | 152 | this.map = L.mapbox.map( this.model.get( 'name' ), 'mapbox.streets', { 153 | center: [ data.value['lat'], data.value['lng'] ], 154 | zoom: 9 155 | } ); 156 | this.marker = L.marker( [ data.value['lat'], data.value['lng'] ], { 157 | draggable: true 158 | } ); 159 | 160 | this.listenTo( this.marker, 'dragend', this.markerDragEnd ); 161 | 162 | this.marker.addTo( this.map ); 163 | }, 164 | markerDragEnd: function( event ) { 165 | var LatLng = event.target.getLatLng(); 166 | this.updateLatLngInputs( LatLng.lat, LatLng.lng ); 167 | }, 168 | updateLatLngInputs: function( lat, lng ) { 169 | console.dir( this.model ); 170 | $( '[name="' + this.model.get( 'name' ) + '[lat]"]', this.$el ).val( this.model.helpers.validateLat( lat ) ); 171 | $( '[name="' + this.model.get( 'name' ) + '[lng]"]', this.$el ).val( this.model.helpers.validateLng( lng ) ); 172 | }, 173 | updateMarker: function() { 174 | console.log( 'update marker' ); 175 | var lat = this.model.helpers.validateLat( $( '[name="' + this.model.get( 'name' ) + '[lat]"]', this.$el ).val() ); 176 | var lng = this.model.helpers.validateLng( $( '[name="' + this.model.get( 'name' ) + '[lng]"]', this.$el ).val() ); 177 | 178 | this.updateLatLngInputs( lat, lng ); // Update after validation 179 | this.marker.setLatLng( [ lat, lng ] ); 180 | }, 181 | get_value: function() { 182 | var lat = $( '[name="' + this.model.get( 'name' ) + '[lat]"]', this.$el ).val(); 183 | var lng = $( '[name="' + this.model.get( 'name' ) + '[lng]"]', this.$el ).val(); 184 | 185 | return { 186 | 'lat': this.model.helpers.validateLat( lat ), 187 | 'lng': this.model.helpers.validateLng( lng ) 188 | }; 189 | } 190 | } ); 191 | 192 | /** 193 | * Add Text Field to OmniBuilder 194 | */ 195 | api.add( 'map', { 196 | model: api.Field_Map, 197 | view: api.Field_Map_View 198 | } ); 199 | 200 | /** 201 | * Extend OmniBuilder API 202 | */ 203 | _.extend( exports.OB, api ); 204 | 205 | } ) ( wp, jQuery ) 206 | -------------------------------------------------------------------------------- /Field/Collection.php: -------------------------------------------------------------------------------- 1 | 18 | 67 | 85 | 86 | children as $child ) { 87 | if ( $child instanceof Custom_Field ) { 88 | $child->render(); 89 | } 90 | } 91 | } 92 | 93 | /** 94 | * @{inheritDoc} 95 | */ 96 | public function set_json_data() { 97 | parent::set_json_data(); 98 | 99 | if ( isset( $this->options['button'] ) && ! empty( $this->options['button'] ) ) { 100 | $this->json_data['button'] = $this->options['button']; 101 | } 102 | 103 | if ( isset( $this->options['limit'] ) && ! empty( $this->options['limit'] ) ) { 104 | $this->json_data['limit'] = $this->options['limit']; 105 | } 106 | } 107 | 108 | /** 109 | * {@inheritdoc} 110 | */ 111 | public function get_posted_value() { 112 | return isset($_POST['_' . $this->parent->get_name() . '_' . $this->id ]) ? $_POST['_' . $this->parent->get_name() . '_' . $this->id] : null; 113 | } 114 | 115 | /** 116 | * {@inheritdoc} 117 | */ 118 | public function enqueue_scripts() { 119 | wp_enqueue_script( 'ob-collection', OMNI_BUILDER_URI . '/Resources/js/Field/Collection.js', array( 'ob-field' ), '0.1', true ); 120 | parent::enqueue_scripts(); 121 | } 122 | 123 | } 124 | 125 | class Collection_Block extends Custom_Field { 126 | /** 127 | * @todo stop this if Collection is not parent element 128 | */ 129 | 130 | /** 131 | * @var string 132 | */ 133 | public $type = 'collection-block'; 134 | 135 | /** 136 | * {@inheritdoc} 137 | */ 138 | public function __construct( $id, array $options = array(), array $children = array() ) { 139 | parent::__construct( $id, $options, $children ); 140 | 141 | $this->children = array_merge( 142 | array( 143 | 'type' => new Hidden( 'type', array( 'default_value' => $id ) ), 144 | ), 145 | $this->children 146 | ); 147 | } 148 | 149 | /** 150 | * {@inheritdoc} 151 | */ 152 | public function render() { ?> 153 | 171 | 172 | children as $child ) { 173 | if ( $child instanceof Custom_Field ) { 174 | $child->render(); 175 | } 176 | } 177 | } 178 | 179 | /** 180 | * @{inheritDoc} 181 | */ 182 | public function set_json_data() { 183 | parent::set_json_data(); 184 | 185 | if ( isset( $this->options['limit'] ) && ! empty( $this->options['limit'] ) ) 186 | $this->json_data['limit'] = $this->options['limit']; 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /Resources/css/ob.css: -------------------------------------------------------------------------------- 1 | /* 2 | * General 3 | */ 4 | .ob-metabox-wrapper { 5 | margin: 0 -12px; 6 | } 7 | .ob-field { 8 | padding: 10px 12px; 9 | box-sizing: border-box; 10 | border-bottom: solid 1px #dedede; 11 | } 12 | .ob-field:last-child { 13 | border-bottom-width: 0; 14 | } 15 | .ob-metabox-wrapper label { 16 | display: block; 17 | font-weight: bold; 18 | margin: 0 0 7px 0; 19 | font-size: 14px; 20 | } 21 | .ob-metabox-wrapper .subfields label { 22 | font-size: 12px; 23 | color: #777; 24 | margin: 0 0 3px 0; 25 | } 26 | .ob-metabox-wrapper .subfields > div { 27 | display: inline-block; 28 | width: auto; 29 | } 30 | .ob-metabox-wrapper .description { 31 | display: block; 32 | font-size: 12px; 33 | font-style: italic; 34 | color: #777; 35 | margin: 0 0 7px 0; 36 | } 37 | .ob-metabox-wrapper input[type=text], 38 | .ob-metabox-wrapper textarea { 39 | font-size: 14px; 40 | padding: 7px; 41 | max-width: 100%; 42 | box-sizing: border-box; 43 | } 44 | .ob-metabox-wrapper input[type=text] { 45 | width: 25em; 46 | } 47 | 48 | /* 49 | * Collections 50 | */ 51 | .ob-collection { 52 | position: relative; 53 | margin: 20px 0; 54 | } 55 | .ob-collection .ob-collection { 56 | padding-left: 20px; 57 | } 58 | .ob-collection .numbers { 59 | display: none; 60 | } 61 | .ob-collection .numbers .size, .ob-collection .numbers .limit { 62 | display: inline; 63 | } 64 | .ob-collection .numbers .size::after { 65 | display: inline; 66 | content: '/'; 67 | } 68 | .ob-collection .messages > div { 69 | display: none; 70 | } 71 | .ob-collection .messages .error { 72 | margin-bottom: 10px; 73 | padding: 10px; 74 | background: #c00; 75 | border-radius: 3px; 76 | border: solid 1px #c00; 77 | color: #fff; 78 | font-size: 12px; 79 | width: 100%; 80 | box-sizing: border-box; 81 | } 82 | .ob-collection .choose { 83 | position: relative; 84 | box-sizing: border-box; 85 | z-index: 1; 86 | } 87 | .ob-collection .choose .blocks { 88 | display: none; 89 | position: absolute; 90 | left: 0; 91 | bottom: 100%; 92 | margin-bottom: 10px; 93 | background: #23282d; 94 | padding: 10px; 95 | z-index: 2; 96 | } 97 | .ob-collection .choose .blocks .block { 98 | color: #eee; 99 | border-bottom: solid 1px #eee; 100 | } 101 | .ob-collection .choose .blocks .block:last-of-type { 102 | border-bottom: none; 103 | } 104 | .ob-collection .choose .blocks .block:hover { 105 | color: #00b9eb; 106 | } 107 | /* header and footer */ 108 | .ob-field__header, .ob-field__footer { 109 | border: solid 1px #dedede; 110 | border-top-left-radius: 4px; 111 | border-top-right-radius: 4px; 112 | padding: 5px 12px; 113 | background: #f5f5f5; 114 | text-align: justify; 115 | line-height: 0; 116 | } 117 | .ob-field__header:after, .ob-field__footer:after { 118 | content: ''; 119 | width: 100%; 120 | display: inline-block; 121 | } 122 | .ob-field__header__titles, .ob-field__header__actions, 123 | .ob-field__footer__titles, .ob-field__footer__actions { 124 | display: inline-block; 125 | vertical-align: middle; 126 | text-align: left; 127 | line-height: 1.4; 128 | margin-bottom: -0.2em; 129 | } 130 | .ob-field__header__actions, .ob-field__footer__actions { 131 | text-align: right; 132 | } 133 | 134 | .ob-field__header__titles .title , .ob-field__footer__titles .title { 135 | margin: 0; 136 | font-size: 14px; 137 | font-weight: bold; 138 | } 139 | 140 | .ob-field__footer { 141 | border-top-left-radius: 0; 142 | border-top-right-radius: 0; 143 | border-bottom-left-radius: 4px; 144 | border-bottom-right-radius: 4px; 145 | } 146 | 147 | /* content */ 148 | .ob-field__content { 149 | border: solid 1px #dedede; 150 | border-top: 0; 151 | border-bottom: 0; 152 | } 153 | 154 | .ob-collection-fieldset .drag-handler, .ob-collection-block .drag-handler { 155 | cursor: move; 156 | } 157 | .ob-collection-fieldset, .ob-collection-block { 158 | position: relative; 159 | background-color: rgba( 0, 0, 0, .005 ); 160 | padding: 0; 161 | } 162 | .ob-collection-fieldset:after, 163 | .ob-collection-block:after { 164 | content: " "; 165 | display: table; 166 | border: none; 167 | -webkit-border-vertical-spacing: 0; 168 | } 169 | .ob-collection-fieldset:after, .ob-collection-block:after { 170 | clear: both; 171 | } 172 | .ob-collection-fieldset .id-wrapper, .ob-collection-block .id-wrapper { 173 | padding: 10px; 174 | color: #333; 175 | text-align: center; 176 | background-color: rgba( 0, 0, 0, .05 ); 177 | width: 40px; 178 | } 179 | .ob-collection-fieldset table, .ob-collection-block table { 180 | width: 100%; 181 | border: none; 182 | -webkit-border-horizontal-spacing: 0; 183 | -webkit-border-vertical-spacing: 0; 184 | table-layout: fixed; 185 | } 186 | .ob-collection-fieldset td, .ob-collection-block td { 187 | padding: 0; 188 | } 189 | .ob-collection-fieldset .remove, .ob-collection-block .remove { 190 | float: right; 191 | margin-right: 20px; 192 | margin-bottom: 20px; 193 | } 194 | .ob-collection.dragging > .ob-field__content > .fields > .ob-collection-fieldset:before, .ob-collection.dragging > .ob-field__content > .fields > .ob-collection-block:before { 195 | position: absolute; 196 | display: block; 197 | content: ''; 198 | top: 0; 199 | right: 0; 200 | bottom: 0; 201 | left: 0; 202 | background: #fff; 203 | opacity: .5; 204 | z-index: 2; 205 | } 206 | .ob-collection.dragging > .ob-field__content > .fields > .ob-collection-fieldset.over:before, .ob-collection.dragging > .ob-field__content > .fields > .ob-collection-block.over:before { 207 | background: #eee; 208 | } 209 | 210 | /* 211 | * Textarea 212 | */ 213 | .ob-field textarea { 214 | min-height: 100px; 215 | } 216 | 217 | /** 218 | * Image 219 | */ 220 | .ob-field-image .preview { 221 | float: none; 222 | position: relative; 223 | display: inline-block; 224 | } 225 | .ob-field-image .preview img { 226 | max-width: 100%; 227 | height: auto; 228 | min-height: 40px; 229 | } 230 | .ob-field-image .ob-field-image-remove { 231 | display: block; 232 | position: absolute; 233 | top: 5px; 234 | right: 5px; 235 | height: 22px; 236 | width: 22px; 237 | padding: 0; 238 | font-size: 20px; 239 | line-height: 20px; 240 | text-align: center; 241 | text-decoration: none; 242 | text-indent: -9999px; 243 | color: transparent; 244 | cursor: pointer; 245 | background-color: #fff; 246 | background-position: -96px 4px; 247 | border-width: 0; 248 | -webkit-border-radius: 3px; 249 | border-radius: 3px; 250 | -webkit-box-shadow: 0 0 0 1px rgba( 0, 0, 0, 0.3 ); 251 | box-shadow: 0 0 0 1px rgba( 0, 0, 0, 0.3 ); 252 | -webkit-transition-duration: none; 253 | transition-duration: none; 254 | -webkit-transition-property: none; 255 | transition-property: none; 256 | } 257 | .ob-field-image .ob-field-image-remove:hover { 258 | -webkit-box-shadow: 0 0 0 1px rgba( 0, 0, 0, 0.6 ); 259 | box-shadow: 0 0 0 1px rgba( 0, 0, 0, 0.6 ); 260 | background-position: -36px 4px; 261 | } 262 | 263 | /* Map */ 264 | .ob-field-map .subfields { 265 | margin-top: 5px; 266 | } 267 | -------------------------------------------------------------------------------- /Resources/js/ob.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress global object 3 | * @module wp 4 | * @type {Object} 5 | */ 6 | window.wp = window.wp || {}; 7 | /** 8 | * Omnibuilder data object 9 | * @type {Object} 10 | */ 11 | window.ob = window.ob || {}; 12 | 13 | /** 14 | * @param {Object} exports WordPress global object `wp` 15 | * @param {Object} data Omnibuilder data object `ob` 16 | * @param {Object} $ jQuery object `jQuery` 17 | */ 18 | ( function( exports, data, $ ) { 19 | 20 | /** 21 | * Check if WordPress template exists 22 | * @function hasTemplate 23 | * @param {String} id Template ID 24 | * @return {Boolean} 25 | */ 26 | exports.hasTemplate = _.memoize(function( id ) { 27 | return $( '#tmpl-' + id ).length ? true : false; 28 | }); 29 | 30 | /** 31 | * OmniBuilder API 32 | * @module OB 33 | * @type {Object} 34 | */ 35 | var api = {}; 36 | 37 | api.toRender = []; 38 | api.callbacks = []; 39 | 40 | var renderIterationIndex = 0; 41 | var renderIterationLength = 0; 42 | api.renderIterationMaxLength = 50; 43 | api.toRenderLength = function() { 44 | var length = api.toRender.length; 45 | return length < api.renderIterationMaxLength ? length : api.renderIterationMaxLength; 46 | }; 47 | 48 | api.renderTick = function() { 49 | if ( 0 === ( renderIterationLength = api.toRenderLength() ) ) { 50 | window.requestAnimationFrame( api.renderTick ); 51 | return; 52 | } 53 | for ( renderIterationIndex = 0, renderIterationLength = api.toRenderLength(); renderIterationIndex < renderIterationLength; renderIterationIndex += 1 ) { 54 | api.toRender[renderIterationIndex].call(); 55 | api.toRender[renderIterationIndex] = null; 56 | } 57 | 58 | api.toRender = _.filter( api.toRender, function( func ) { return null !== func; } ); 59 | 60 | window.requestAnimationFrame( api.renderTick ); 61 | return; 62 | }; 63 | 64 | api.render = function( func ) { 65 | if ( 'function' === typeof func ) { 66 | api.toRender.push( func ); 67 | } 68 | }; 69 | 70 | /** 71 | * Create Fields from given data 72 | * @private 73 | * @function FieldConstructor 74 | * @param {Object} data Fields informations (id, label, default_value, ...) 75 | * @param {Object} options Currently you can only define fields parent object 76 | * @return {Object} fields Created fields 77 | */ 78 | api.FieldConstructor = function( data, options ) { 79 | var options = options || {}; 80 | var data = data || {}; 81 | var parent = options.parent || false; 82 | var fields = {}; 83 | 84 | for ( key in data ) { 85 | data[key].id = key; 86 | if ( parent ) data[key].parent = parent; 87 | 88 | var constructor = api.FieldConstructor.types[ data[key].type ] || api.Field; 89 | fields[key] = new constructor( data[key] ); 90 | } 91 | 92 | return fields; 93 | }; 94 | 95 | /** 96 | * All Field models 97 | * @private 98 | * @type {Object} 99 | */ 100 | api.FieldConstructor.types = {}; 101 | 102 | 103 | /** 104 | * Create and add Field view object to his model object 105 | * @private 106 | * @function FieldViewConstructor 107 | * @param {Object} data Options param of Field view object 108 | * @return {Void} 109 | */ 110 | api.FieldViewConstructor = function( data ) { 111 | var data = data || {}; 112 | var model = data.model || false; 113 | 114 | if ( !model ) return; 115 | 116 | var constructor = api.FieldViewConstructor.types[ model.get( 'type' ) ] || api.Field_View; 117 | model.view = new constructor( data ); 118 | }; 119 | 120 | /** 121 | * All Field views 122 | * @private 123 | * @type {Object} 124 | */ 125 | api.FieldViewConstructor.types = {}; 126 | 127 | /** 128 | * Add your Field model and view object to OmniBuilder 129 | * @function add 130 | * @param {String} type Field type 131 | * @param {Object} options Define your model and view 132 | * @return {Boolean} 133 | * @example api.add( 'myfield', { model: api.MyField, view: api.MyFieldView } ) 134 | */ 135 | api.add = function( type, options ) { 136 | var type = type || false; 137 | var options = options || {}; 138 | var data; 139 | 140 | if ( !type ) return false; 141 | 142 | if ( options.model ) { 143 | data = {}; 144 | data[type] = options.model; 145 | 146 | _.extend( api.FieldConstructor.types, data ); 147 | } 148 | 149 | if ( options.view ) { 150 | data = {}; 151 | data[type] = options.view; 152 | 153 | _.extend( api.FieldViewConstructor.types, data ); 154 | } 155 | 156 | return true; 157 | } 158 | 159 | /** 160 | * OmniBuilder model 161 | * @private 162 | * @class OmniBuilder 163 | * @extends {Backbone.Model} 164 | */ 165 | api.OmniBuilder = Backbone.Model.extend( { 166 | /** 167 | * Default options 168 | * @private 169 | * @memberOf OmniBuilder 170 | * @type {Object} 171 | */ 172 | defaults: { 173 | 'metaboxes': {}, 174 | 'rendered': false 175 | }, 176 | /** 177 | * Constructor 178 | * @private 179 | * @memberOf OmniBuilder 180 | * @param {Object} attrs Metabox informations 181 | * @param {Object} options 182 | * @return {OmniBuilder} `this` 183 | */ 184 | constructor: function( attrs, options ) { 185 | var attrs = attrs || {}; 186 | 187 | Backbone.Model.apply( this, [ {}, options ] ); 188 | this.add_metaboxes( attrs ); 189 | this.render(); 190 | 191 | return this; 192 | }, 193 | /** 194 | * Here we listen to some events 195 | * @private 196 | * @memberOf OmniBuilder 197 | */ 198 | initialize: function() { 199 | this.listenTo( api.postForm, 'generate_names', this.generate_names ); 200 | }, 201 | /** 202 | * Create metaboxes from given data 203 | * @private 204 | * @memberOf OmniBuilder 205 | * @param {Object} data Metaboxes informations 206 | * @return {OmniBuilder} `this` 207 | */ 208 | add_metaboxes: function( data ) { 209 | for ( key in data ) { 210 | this.add_metabox( key, data[key] ); 211 | } 212 | 213 | return this; 214 | }, 215 | /** 216 | * Create metabox 217 | * @private 218 | * @memberOf OmniBuilder 219 | * @requires Metabox 220 | * @param {String} key Metabox ID 221 | * @param {Object} data Metabox informations 222 | * @return {OmniBuilder} `this` 223 | */ 224 | add_metabox: function( key, data ) { 225 | var metaboxes = this.get( 'metaboxes' ); 226 | 227 | if ( typeof data.isValid !== 'function' ) { 228 | data.id = key; 229 | data = new api.Metabox( data ); 230 | } 231 | 232 | if ( data.isValid() ) 233 | metaboxes[ key ] = data; 234 | 235 | this.set( 'metaboxes', metaboxes ); 236 | 237 | return this; 238 | }, 239 | /** 240 | * Render each metabox 241 | * @private 242 | * @memberOf OmniBuilder 243 | * @return {OmniBuilder} `this` 244 | */ 245 | render: function() { 246 | var metaboxes = this.get( 'metaboxes' ); 247 | for ( key in metaboxes ) 248 | metaboxes[key].render(); 249 | this.set( 'rendered', true ); 250 | 251 | return this; 252 | }, 253 | /** 254 | * Call generate_names function on each metabox 255 | * @private 256 | * @memberOf OmniBuilder 257 | * @return {OmniBuilder} `this` 258 | */ 259 | generate_names: function() { 260 | var metaboxes = this.get( 'metaboxes' ); 261 | for ( key in metaboxes ) 262 | metaboxes[key].generate_names( key ); 263 | 264 | return this; 265 | } 266 | } ); 267 | 268 | /** 269 | * Empty object 270 | * @type {Object} 271 | */ 272 | api.postForm = {}; 273 | 274 | /** 275 | * WordPress post form view 276 | * @private 277 | * @class postForm_View 278 | * @extends {Backbone.View} 279 | */ 280 | api.postForm_View = Backbone.View.extend( { 281 | /** 282 | * Form ID 283 | * @private 284 | * @memberOf postForm_View 285 | * @type {String} 286 | */ 287 | el: '#post', 288 | /** 289 | * Listen to events 290 | * @private 291 | * @memberOf postForm_View 292 | * @type {Object} 293 | */ 294 | events: { 295 | 'submit': 'submit' 296 | }, 297 | /** 298 | * Trigger generate_names event on form submit event 299 | * @private 300 | * @memberOf postForm_View 301 | */ 302 | submit: function( event ) { 303 | this.trigger( 'generate_names' ); 304 | } 305 | } ); 306 | 307 | $( document ).ready( function() { 308 | api.postForm = new api.postForm_View(); 309 | 310 | if ( data ) { 311 | window.ob_test = new api.OmniBuilder( _.extend( {}, data ) ); 312 | } 313 | 314 | window.requestAnimationFrame( api.renderTick ); 315 | } ); 316 | 317 | /** 318 | * Export api to wp.OB 319 | */ 320 | exports.OB = api; 321 | 322 | } ) ( wp, ob, jQuery ) -------------------------------------------------------------------------------- /Resources/js/Field.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress global object 3 | * @type {Object} 4 | */ 5 | window.wp = window.wp || {}; 6 | 7 | /** 8 | * @param {Object} exports WordPress global object `wp` 9 | * @param {Object} $ jQuery object `jQuery` 10 | */ 11 | ( function( exports, $ ) { 12 | 13 | /** 14 | * Load OmniBuilder API 15 | * @module OB 16 | * @type {Object} 17 | */ 18 | var api = exports.OB || {}; 19 | 20 | /** 21 | * Setting model handles value changing 22 | * @class Setting 23 | * @extends {Backbone.Model} 24 | */ 25 | api.Setting = Backbone.Model.extend( { 26 | /** 27 | * Default options 28 | * @memberOf Setting 29 | * @type {Object} 30 | */ 31 | defaults: { 32 | 'default_value': '', 33 | 'value': null, 34 | 'field': null 35 | }, 36 | /** 37 | * Initialize Setting model 38 | * @memberOf Setting 39 | */ 40 | initialize: function() { 41 | // If value has not been setted then set a default_value 42 | if ( this.get( 'value' ) == null ) 43 | this.set( 'value', this.get( 'default_value' ) ); 44 | 45 | this.on( 'change:value', this.changeValue ); 46 | }, 47 | changeValue: function( model, current, options ) { 48 | if ( current == undefined || current == null ) 49 | this.set( 'value', this.get( 'default_value' ), { silent: true } ); 50 | } 51 | } ); 52 | 53 | /** 54 | * Default Field model 55 | * @class Field 56 | * @extends {Backbone.Model} 57 | */ 58 | api.Field = Backbone.Model.extend( { 59 | /** 60 | * Default options 61 | * @memberOf Field 62 | * @type {Object} 63 | */ 64 | defaults: { 65 | 'id': '', 66 | 'label': '', 67 | 'name': '', 68 | 'description': '', 69 | 'default_value': '', 70 | 'attributes': {}, 71 | 'rendered': false, 72 | 'can_be_focused': true 73 | }, 74 | /** 75 | * Initialize Field model 76 | * @memberOf Field 77 | */ 78 | initialize: function() { 79 | this.set( 'name', this.cid ); 80 | this.set( 'setting', new api.Setting( { default_value: this.get( 'default_value' ), field: this } ) ); 81 | this.on( 'focus', this.focus ); 82 | }, 83 | /** 84 | * Set setting value 85 | * @memberOf Field 86 | * @param {String|Array|Object} data Field value 87 | * @return {Field} `this` 88 | */ 89 | set_settings: function( data ) { 90 | this.get( 'setting' ).set( 'value', data ); 91 | 92 | return this; 93 | }, 94 | /** 95 | * Render Field View 96 | * @memberOf Field 97 | * @return {Field} `this` 98 | */ 99 | render: function() { 100 | if ( ! this.view ) 101 | api.FieldViewConstructor( { model: this } ); 102 | 103 | this.trigger( 'render' ); 104 | 105 | return this; 106 | }, 107 | /** 108 | * Generate Field name 109 | * @memberOf Field 110 | * @return {Field} `this` 111 | */ 112 | generate_names: function() { 113 | var 114 | id = this.get( 'id' ), 115 | parent = this.get( 'parent' ); 116 | var name = ''; 117 | 118 | if ( parent ) 119 | name += parent.get( 'name' ); 120 | 121 | if ( ( id + '' ).length ) 122 | name += '_' + id; 123 | 124 | this.set( 'name', name ); 125 | 126 | return this; 127 | }, 128 | /** 129 | * Collect values 130 | * @memberOf Field 131 | * @return {Field} `this` 132 | */ 133 | collect_values: function() { 134 | var value = this.view.get_value(); 135 | this.get( 'setting' ).set( 'value', value ); 136 | }, 137 | focus: function() { 138 | if ( ! this.view ) { 139 | this.once( 'render', this.focus ); 140 | return; 141 | } 142 | this.view.trigger( 'focus' ); 143 | } 144 | } ); 145 | 146 | /** 147 | * Default Field view 148 | * @class Field_View 149 | * @extends {Backbone.View} 150 | */ 151 | api.Field_View = Backbone.View.extend( { 152 | appended: false, 153 | /** 154 | * DOM Element class attribute 155 | * @memberOf Field_View 156 | * @type {String} 157 | */ 158 | className: 'ob-field', 159 | /** 160 | * Find Field template and generate DOM Element 161 | * @memberOf Field_View 162 | * @param {Object} data Field template data 163 | * @return {Element} 164 | */ 165 | template: function( data ) { 166 | var id = wp.hasTemplate( 'ob-field-' + this.model.get( 'type' ) ) ? 'ob-field-' + this.model.get( 'type' ) : 'ob-field'; 167 | return wp.template( id )( data ); 168 | }, 169 | /** 170 | * Listen to some model events 171 | * @memberOf Field_View 172 | */ 173 | initialize: function() { 174 | this.listenTo( this.model, 'render', this.render ); 175 | this.listenTo( this.model, 'change:name', this.updateName ); 176 | this.on( 'appended', function() { 177 | this.appended = true; 178 | } ); 179 | this.on( 'focus', this.focus ); 180 | 181 | this.trigger( 'initialize' ); 182 | }, 183 | /** 184 | * Field render 185 | * @memberOf Field_View 186 | * @return {Field_View} `this` 187 | */ 188 | attributesToString: function( data ) { 189 | var attrs = data || {}; 190 | 191 | if ( _.size( attrs ) < 1 ) return ''; 192 | 193 | var attributes = ''; 194 | for ( attr in attrs ) { 195 | attributes += ' ' + attr.toLowerCase() + '=' + attrs[ attr ] + ''; 196 | } 197 | 198 | return attributes; 199 | }, 200 | /** 201 | * Parse data 202 | * @param {Object} data 203 | * @return {Object} data 204 | */ 205 | parseData: function( data ) { 206 | return data; 207 | }, 208 | getData: function() { 209 | var data = {}; 210 | _.extend( data, this.model.attributes ); 211 | if ( this.model.has( 'setting' ) ) 212 | _.extend( data, this.model.get( 'setting' ).attributes ); 213 | 214 | data.attributes = this.attributesToString( data.attributes ); 215 | 216 | return this.parseData( data ); 217 | }, 218 | render: function() { 219 | if ( ! this.appended ) { 220 | var rendered = false; 221 | 222 | if ( this.model.get( 'rendered' ) !== true ) { 223 | var data = this.getData(); 224 | 225 | this.$el.html( this.template( data ) ); 226 | this.model.set( 'rendered', true ); 227 | rendered = true; 228 | } 229 | 230 | this.appendToParent(); 231 | 232 | if ( rendered ) { 233 | this.trigger( 'render' ); 234 | } 235 | } else { 236 | api.render( function() { 237 | var data = this.getData(); 238 | 239 | this.$el.html( this.template( data ) ); 240 | this.model.set( 'rendered', true ); 241 | this.trigger( 'render' ); 242 | }.bind( this) ); 243 | } 244 | 245 | return this; 246 | }, 247 | /** 248 | * Returns fields element 249 | * @memberOf Field_View 250 | * @return {Element} 251 | */ 252 | getFieldsEl: function() { 253 | if ( this.$fields ) return this.$fields; 254 | 255 | var fields = $( $( '.fields', this.$el )[0] ); 256 | if ( ! fields.length ) { 257 | this.$el.append( '
' ); 258 | fields = $( $( '.fields', this.$el )[0] ); 259 | } 260 | 261 | this.$fields = fields; 262 | 263 | return this.$fields; 264 | }, 265 | /** 266 | * Append view element to parent element 267 | * @memberOf Field_View 268 | * @return {Field_View} `this` 269 | */ 270 | appendToParent: function() { 271 | var parent = this.model.get( 'parent' ); 272 | if ( parent && parent.view && false === parent.view.appended && parent.view.getFieldsEl().find( this.$el ).length < 1 ) { 273 | parent.view.getFieldsEl().append( this.$el ); 274 | this.listenTo( parent.view, 'appended', function() { 275 | this.trigger( 'appended' ); 276 | } ); 277 | } else { 278 | api.render( function() { 279 | var parent = this.model.get( 'parent' ); 280 | if ( parent && parent.view.getFieldsEl().find( this.$el ).length < 1 ) { 281 | parent.view.getFieldsEl().append( this.$el ); 282 | 283 | api.render( function() { 284 | this.trigger( 'appended' ); 285 | }.bind( this ) ); 286 | } 287 | }.bind( this ) ); 288 | } 289 | 290 | return this; 291 | }, 292 | /** 293 | * Replace old name with new name 294 | * @memberOf Field_View 295 | * @param {Object} model This model 296 | * @param {String} value New value 297 | * @param {Object} options 298 | */ 299 | updateName: function( model, value, options ) { 300 | var prevValue = model.previous( 'name' ); 301 | $( '[name^="' + prevValue + '"]', this.$el ).each( function() { 302 | var $self = $( this ); 303 | var name = $self.attr( 'name' ); 304 | var id = $self.attr( 'id' ); 305 | 306 | var new_attrs = {}; 307 | 308 | if ( name !== undefined && name.length ) 309 | new_attrs[ 'name' ] = name.replace( prevValue, value ); 310 | 311 | if ( id !== undefined && id.length ) 312 | new_attrs[ 'id' ] = id.replace( prevValue, value ); 313 | 314 | $self.attr( new_attrs ); 315 | } ); 316 | }, 317 | /** 318 | * Gets value of live DOM input element 319 | * @return {string|object|null} Returns null if there is not input elements, string if there is one input element and object if there is two or more input elements. 320 | */ 321 | get_value: function() { 322 | var input_els = $( 'input, textarea, select', this.$el ); 323 | 324 | if ( input_els.length < 1 ) 325 | return null; 326 | 327 | if ( input_els == 1 ) 328 | return input_els.val(); 329 | 330 | var value = {}; 331 | var el; 332 | for ( var i = 0, l = input_els.length; i < l; i++ ) { 333 | el = input_els.eq( i ); 334 | value[ el.attr( 'id' ) ] = el.val(); 335 | } 336 | 337 | return value; 338 | }, 339 | focus: function() { 340 | if ( this.appended ) { 341 | $( 'input, textarea, select', this.$el ).first().focus(); 342 | } else { 343 | this.once( 'appended', function() { 344 | $( 'input, textarea, select', this.$el ).first().focus(); 345 | } ); 346 | } 347 | } 348 | } ); 349 | 350 | /** 351 | * Extend OmniBuilder API 352 | */ 353 | _.extend( exports.OB, api ); 354 | 355 | } ) ( wp, jQuery ) -------------------------------------------------------------------------------- /Inflector.php: -------------------------------------------------------------------------------- 1 | '1zes', 35 | '/^(ox)$/i' => '1en', 36 | '/([m|l])ouse$/i' => '1ice', 37 | '/(matr|vert|ind)ix|ex$/i' => '1ices', 38 | '/(x|ch|ss|sh)$/i' => '1es', 39 | '/([^aeiouy]|qu)ies$/i' => '1y', 40 | '/([^aeiouy]|qu)y$/i' => '1ies', 41 | '/(hive)$/i' => '1s', 42 | '/(?:([^f])fe|([lr])f)$/i' => '12ves', 43 | '/sis$/i' => 'ses', 44 | '/([ti])um$/i' => '1a', 45 | '/(buffal|tomat)o$/i' => '1oes', 46 | '/(bu)s$/i' => '1ses', 47 | '/(alias|status)/i'=> '1es', 48 | '/(octop|vir)us$/i'=> '1i', 49 | '/(ax|test)is$/i'=> '1es', 50 | '/s$/i'=> 's', 51 | '/$/'=> 's'); 52 | 53 | $uncountable = array('equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep'); 54 | 55 | $irregular = array( 56 | 'person' => 'people', 57 | 'man' => 'men', 58 | 'child' => 'children', 59 | 'sex' => 'sexes', 60 | 'move' => 'moves'); 61 | 62 | $lowercased_word = strtolower($word); 63 | 64 | foreach ($uncountable as $_uncountable){ 65 | if(substr($lowercased_word,(-1*strlen($_uncountable))) == $_uncountable){ 66 | return $word; 67 | } 68 | } 69 | 70 | foreach ($irregular as $_plural=> $_singular){ 71 | if (preg_match('/('.$_plural.')$/i', $word, $arr)) { 72 | return preg_replace('/('.$_plural.')$/i', substr($arr[0],0,1).substr($_singular,1), $word); 73 | } 74 | } 75 | 76 | foreach ($plural as $rule => $replacement) { 77 | if (preg_match($rule, $word)) { 78 | return preg_replace($rule, $replacement, $word); 79 | } 80 | } 81 | return false; 82 | 83 | } 84 | 85 | /** 86 | * Singularizes English nouns. 87 | * 88 | * @access public 89 | * @static 90 | * @param string $word English noun to singularize 91 | * @return string Singular noun. 92 | */ 93 | public static function singularize($word) { 94 | $singular = array ( 95 | '/(quiz)zes$/i' => '\1', 96 | '/(matr)ices$/i' => '\1ix', 97 | '/(vert|ind)ices$/i' => '\1ex', 98 | '/^(ox)en/i' => '\1', 99 | '/(alias|status)es$/i' => '\1', 100 | '/([octop|vir])i$/i' => '\1us', 101 | '/(cris|ax|test)es$/i' => '\1is', 102 | '/(shoe)s$/i' => '\1', 103 | '/(o)es$/i' => '\1', 104 | '/(bus)es$/i' => '\1', 105 | '/([m|l])ice$/i' => '\1ouse', 106 | '/(x|ch|ss|sh)es$/i' => '\1', 107 | '/(m)ovies$/i' => '\1ovie', 108 | '/(s)eries$/i' => '\1eries', 109 | '/([^aeiouy]|qu)ies$/i' => '\1y', 110 | '/([lr])ves$/i' => '\1f', 111 | '/(tive)s$/i' => '\1', 112 | '/(hive)s$/i' => '\1', 113 | '/([^f])ves$/i' => '\1fe', 114 | '/(^analy)ses$/i' => '\1sis', 115 | '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', 116 | '/([ti])a$/i' => '\1um', 117 | '/(n)ews$/i' => '\1ews', 118 | '/s$/i' => '', 119 | ); 120 | 121 | $uncountable = array('equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep'); 122 | 123 | $irregular = array( 124 | 'person' => 'people', 125 | 'man' => 'men', 126 | 'child' => 'children', 127 | 'sex' => 'sexes', 128 | 'move' => 'moves'); 129 | 130 | $lowercased_word = strtolower($word); 131 | foreach ($uncountable as $_uncountable){ 132 | if(substr($lowercased_word,(-1*strlen($_uncountable))) == $_uncountable){ 133 | return $word; 134 | } 135 | } 136 | 137 | foreach ($irregular as $_plural=> $_singular){ 138 | if (preg_match('/('.$_singular.')$/i', $word, $arr)) { 139 | return preg_replace('/('.$_singular.')$/i', substr($arr[0],0,1).substr($_plural,1), $word); 140 | } 141 | } 142 | 143 | foreach ($singular as $rule => $replacement) { 144 | if (preg_match($rule, $word)) { 145 | return preg_replace($rule, $replacement, $word); 146 | } 147 | } 148 | 149 | return $word; 150 | } 151 | 152 | /** 153 | * Converts an underscored or CamelCase word into a English 154 | * sentence. 155 | * 156 | * The titleize function converts text like "WelcomePage", 157 | * "welcome_page" or "welcome page" to this "Welcome 158 | * Page". 159 | * If second parameter is set to 'first' it will only 160 | * capitalize the first character of the title. 161 | * 162 | * @access public 163 | * @static 164 | * @param string $word Word to format as tile 165 | * @param string $uppercase If set to 'first' it will only uppercase the 166 | * first character. Otherwise it will uppercase all 167 | * the words in the title. 168 | * @return string Text formatted as title 169 | */ 170 | public static function titleize($word, $uppercase = '') { 171 | $uppercase = $uppercase == 'first' ? 'ucfirst' : 'ucwords'; 172 | return $uppercase(Inflector::humanize(Inflector::underscore($word))); 173 | } 174 | 175 | /** 176 | * Returns given word as CamelCased 177 | * 178 | * Converts a word like "send_email" to "SendEmail". It 179 | * will remove non alphanumeric character from the word, so 180 | * "who's online" will be converted to "WhoSOnline" 181 | * 182 | * @access public 183 | * @static 184 | * @see variablize 185 | * @param string $word Word to convert to camel case 186 | * @return string UpperCamelCasedWord 187 | */ 188 | public static function camelize($word) { 189 | return str_replace(' ','',ucwords(preg_replace('/[^A-Z^a-z^0-9]+/',' ',$word))); 190 | } 191 | 192 | /** 193 | * Converts a word "into_it_s_underscored_version" 194 | * 195 | * Convert any "CamelCased" or "ordinary Word" into an 196 | * "underscored_word". 197 | * 198 | * This can be really useful for creating friendly URLs. 199 | * 200 | * @access public 201 | * @static 202 | * @param string $word Word to underscore 203 | * @return string Underscored word 204 | */ 205 | public static function underscore($word) { 206 | return strtolower(preg_replace('/[^A-Z^a-z^0-9]+/','_', 207 | preg_replace('/([a-zd])([A-Z])/','1_2', 208 | preg_replace('/([A-Z]+)([A-Z][a-z])/','1_2',$word)))); 209 | } 210 | 211 | /** 212 | * Returns a human-readable string from $word 213 | * 214 | * Returns a human-readable string from $word, by replacing 215 | * underscores with a space, and by upper-casing the initial 216 | * character by default. 217 | * 218 | * If you need to uppercase all the words you just have to 219 | * pass 'all' as a second parameter. 220 | * 221 | * @access public 222 | * @static 223 | * @param string $word String to "humanize" 224 | * @param string $uppercase If set to 'all' it will uppercase all the words 225 | * instead of just the first one. 226 | * @return string Human-readable word 227 | */ 228 | public static function humanize($word, $uppercase = '') { 229 | $uppercase = $uppercase == 'all' ? 'ucwords' : 'ucfirst'; 230 | return $uppercase(str_replace('_',' ',preg_replace('/_id$/', '',$word))); 231 | } 232 | 233 | /** 234 | * Same as camelize but first char is underscored 235 | * 236 | * Converts a word like "send_email" to "sendEmail". It 237 | * will remove non alphanumeric character from the word, so 238 | * "who's online" will be converted to "whoSOnline" 239 | * 240 | * @access public 241 | * @static 242 | * @see camelize 243 | * @param string $word Word to lowerCamelCase 244 | * @return string Returns a lowerCamelCasedWord 245 | */ 246 | public static function variablize($word) { 247 | $word = Inflector::camelize($word); 248 | return strtolower($word[0]).substr($word,1); 249 | } 250 | 251 | /** 252 | * Converts a class name to its table name according to rails 253 | * naming conventions. 254 | * 255 | * Converts "Person" to "people" 256 | * 257 | * @access public 258 | * @static 259 | * @see classify 260 | * @param string $class_name Class name for getting related table_name. 261 | * @return string plural_table_name 262 | */ 263 | public static function tableize($class_name) { 264 | return Inflector::pluralize(Inflector::underscore($class_name)); 265 | } 266 | 267 | // }}} 268 | // {{{ classify() 269 | 270 | /** 271 | * Converts a table name to its class name according to rails 272 | * naming conventions. 273 | * 274 | * Converts "people" to "Person" 275 | * 276 | * @access public 277 | * @static 278 | * @see tableize 279 | * @param string $table_name Table name for getting related ClassName. 280 | * @return string SingularClassName 281 | */ 282 | public static function classify($table_name) { 283 | return Inflector::camelize(Inflector::singularize($table_name)); 284 | } 285 | 286 | /** 287 | * Converts number to its ordinal English form. 288 | * 289 | * This method converts 13 to 13th, 2 to 2nd ... 290 | * 291 | * @access public 292 | * @static 293 | * @param integer $number Number to get its ordinal value 294 | * @return string Ordinal representation of given string. 295 | */ 296 | public static function ordinalize($number) { 297 | if (in_array(($number % 100),range(11,13))){ 298 | return $number.'th'; 299 | }else{ 300 | switch (($number % 10)) { 301 | case 1: 302 | return $number.'st'; 303 | break; 304 | case 2: 305 | return $number.'nd'; 306 | break; 307 | case 3: 308 | return $number.'rd'; 309 | default: 310 | return $number.'th'; 311 | break; 312 | } 313 | } 314 | } 315 | 316 | } 317 | -------------------------------------------------------------------------------- /Resources/js/Field/Collection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress global variable 3 | * @type {Object} 4 | */ 5 | window.wp = window.wp || {}; 6 | 7 | /** 8 | * @param {Object} exports WordPress global object `wp` 9 | * @param {Object} $ jQuery object `jQuery` 10 | */ 11 | ( function( exports, $ ) { 12 | 13 | /** 14 | * Load OmniBuilder API 15 | * @module OB 16 | * @type {Object} 17 | */ 18 | var api = exports.OB || {}; 19 | 20 | /** 21 | * Collection Setting model 22 | * @class Collection_Setting 23 | * @extends {Backbone.Collection} 24 | */ 25 | api.Collection_Setting = Backbone.Collection.extend( { 26 | /** 27 | * Field object 28 | * @memberOf Collection_Setting 29 | * @type {Object|null} 30 | */ 31 | field: null, 32 | /** 33 | * Collection Setting constructor 34 | * @memberOf Collection_Setting 35 | * @param {Object} attrs Currently uses only field 36 | * @param {Array} models 37 | * @param {Object} options 38 | * @return {Collection_Setting} `this` 39 | */ 40 | constructor: function( attrs, models, options ) { 41 | this.field = attrs.field; 42 | Backbone.Collection.apply( this, [ models, options ] ); 43 | 44 | return this; 45 | }, 46 | /** 47 | * Initialize Collection Setting 48 | * @memberOf Collection_Setting 49 | */ 50 | initialize: function() { 51 | this.on( 'remove reset reindex', this.reIndex ); 52 | this.on( 'add', this.renderNew ); 53 | }, 54 | /** 55 | * Sort Collection Fieldsets by ID 56 | * @memberOf Collection_Setting 57 | * @param {Object} model Fieldset 58 | */ 59 | comparator: function( model ) { 60 | return model.get( 'id' ); 61 | }, 62 | /** 63 | * Add a Collection Fieldset, or list of Fieldsets to the set. 64 | * @memberOf Collection_Setting 65 | * @param {Array|Object} models Collection Fieldsets 66 | * @param {Object} options 67 | * @return {Boolean|Collection_Fieldset|Array} Returns false if limit is 68 | * exceeded otherwise returns the added (or merged) Collection Fieldset 69 | * (or Collection Fieldsets). 70 | */ 71 | add: function( models, options ) { 72 | if ( this.field.get( 'limit' ) >= 0 ) { 73 | if ( this.size() >= this.field.get( 'limit' ) ) { 74 | return false; 75 | } 76 | } 77 | 78 | if ( options && options.move == true ) 79 | return Backbone.Collection.prototype.add.apply( this, [ models, options ] ); 80 | 81 | var model_type = this.field.get( 'model-type' ); 82 | 83 | var data = { 84 | id: this.size(), 85 | fields: ( model_type == 'collection-block' ? this.field.get( 'fields' )[ this.field.get( 'blocks' )[ models.block ].index ].fields : this.field.get( 'fields' ) ), 86 | parent: this.field, 87 | type: model_type 88 | }; 89 | if ( model_type == 'collection-block' ) 90 | _.reject( models, function( value, key ) { 91 | return key == 'block'; 92 | } ); 93 | 94 | options = models; 95 | 96 | if ( model_type == 'collection-block' ) { 97 | models = new api.Collection_Block( data ); 98 | } 99 | else 100 | models = new api.Collection_Fieldset( data ); 101 | 102 | return this.set( models, _.extend( { merge: false }, options, { add: true, remove: false } ) ); 103 | }, 104 | /** 105 | * Reset Collection Fieldsets IDs 106 | * @memberOf Collection_Setting 107 | * @return {Collection_Setting} `this` 108 | */ 109 | reIndex: function() { 110 | this.forEach( function( model ) { 111 | if ( model.get( 'id' ) !== this.indexOf( model ) ) 112 | model.set( 'id', this.indexOf( model ) ); 113 | }.bind( this ) ); 114 | 115 | return this; 116 | }, 117 | /** 118 | * Render model 119 | * @memberOf Collection_Setting 120 | * @param {Object} model 121 | * @return {Collection_Setting} `this` 122 | */ 123 | renderNew: function( model ) { 124 | model.render(); 125 | 126 | return this; 127 | } 128 | } ); 129 | 130 | /** 131 | * Collection model 132 | * @class Collection 133 | * @extends {Field} 134 | */ 135 | api.Collection = api.Field.extend( { 136 | /** 137 | * Default options 138 | * @memberOf Collection 139 | * @type {Object} 140 | * @extends {Field.prototype.defaults} 141 | */ 142 | defaults: _.extend( {}, api.Field.prototype.defaults, { 143 | 'fields': '', 144 | 'limit': -1, 145 | 'dragging': false, 146 | 'blocks': {}, 147 | 'button': 'Add', 148 | 'model-type': 'collection-fieldset', 149 | 'type': 'collection' 150 | } ), 151 | /** 152 | * Initialize Collection model 153 | * @memberOf Collection 154 | */ 155 | initialize: function() { 156 | var fields = this.get( 'fields' ); 157 | if ( _.size( fields ) > 0 ) { 158 | var _blocks = _.filter( fields, function( field ) { 159 | return field.type == 'collection-block'; 160 | } ); 161 | 162 | if ( _.size( _blocks ) > 0 ) { 163 | var blocks = {}; 164 | _.each( _blocks, function( field, index ) { 165 | if ( field.type == 'collection-block' ) { 166 | blocks[ field.id ] = { 167 | index: index, 168 | label: field.label 169 | }; 170 | } 171 | } ); 172 | 173 | this.set( 'fields', _blocks ); 174 | this.set( 'blocks', blocks ); 175 | 176 | if ( this.get( 'button' ) === this.defaults['button'] ) { 177 | this.set( 'button', 'Choose' ); 178 | } 179 | 180 | this.set( 'model-type', 'collection-block' ); 181 | } 182 | } 183 | this.set( 'setting', new api.Collection_Setting( { field: this } ) ); 184 | }, 185 | /** 186 | * @memberOf Collection 187 | * @inheritDoc 188 | */ 189 | set_settings: function( data ) { 190 | var data = _.isArray( data ) ? data : []; 191 | for ( var i = 0; i < data.length; i++ ) { 192 | if ( _.size( this.get( 'blocks' ) ) > 0 && data[i].type ) 193 | this.get( 'setting' ).add( { block: data[i].type, silent: true } ).set_settings( data[i] ); 194 | else 195 | this.get( 'setting' ).add( { silent: true } ).set_settings( data[i] ); 196 | } 197 | 198 | return this; 199 | }, 200 | /** 201 | * @memberOf Collection 202 | * @inheritDoc 203 | */ 204 | render: function() { 205 | if ( ! this.view ) { 206 | api.FieldViewConstructor( { model: this } ); 207 | this.listenTo( this.view, 'render', this.render_fields ); 208 | } 209 | 210 | this.trigger( 'render' ); 211 | 212 | return this; 213 | }, 214 | /** 215 | * Render Collection Fieldsets 216 | * @memberOf Collection 217 | * @return {Collection} `this` 218 | */ 219 | render_fields: function() { 220 | this.get( 'setting' ).forEach( function( fieldset ) { 221 | fieldset.render(); 222 | } ); 223 | 224 | return this; 225 | }, 226 | /** 227 | * Move Collection Fieldset to new index 228 | * @memberOf Collection 229 | * @param {Object} model Collection Fieldset 230 | * @param {Number} index New index 231 | */ 232 | move: function( model, index ) { 233 | var setting = this.get( 'setting' ); 234 | var oldindex = setting.indexOf( model ); 235 | if ( oldindex > -1 ) { 236 | setting.remove( model, { silent: true } ); 237 | setting.add( model, { at: index, silent: true, move: true } ); 238 | setting.trigger( 'reindex' ); 239 | model.trigger( 'move', index ); 240 | } 241 | }, 242 | /** 243 | * Generate Collection Fieldsets names 244 | * @memberOf Collection 245 | * @return {Collection} `this` 246 | */ 247 | generate_names: function() { 248 | api.Field.prototype.generate_names.apply( this ); 249 | 250 | if ( this.get( 'setting' ).size() > 0 ) { 251 | this.get( 'setting' ).forEach( function( fieldset ) { 252 | fieldset.generate_names(); 253 | } ); 254 | } 255 | else 256 | this.trigger( 'addEmptyField' ); 257 | 258 | return this; 259 | }, 260 | /** 261 | * Size of collection 262 | * @return {Number} 263 | */ 264 | size: function() { 265 | return this.get( 'setting' ).size(); 266 | } 267 | } ); 268 | 269 | /** 270 | * Collection view 271 | * @class Collection_View 272 | * @extends {Field_View} 273 | */ 274 | api.Collection_View = api.Field_View.extend( { 275 | /** 276 | * @memberOf Collection_View 277 | * @inheritDoc 278 | */ 279 | className: 'ob-field ob-collection', 280 | /** 281 | * @memberOf Collection_View 282 | * @inheritDoc 283 | */ 284 | template: wp.template( 'ob-collection' ), 285 | /** 286 | * Listen to events 287 | * @memberOf Collection_View 288 | * @type {Object} 289 | */ 290 | events: { 291 | 'click div:not(.ob-field) .add': 'add', 292 | 'click div:not(.ob-field) .choose': 'toggleBlocks', 293 | 'click div:not(.ob-field) .choose .blocks [data-block]': 'addBlock' 294 | }, 295 | /** 296 | * @memberOf Collection_View 297 | * @inheritDoc 298 | */ 299 | initialize: function() { 300 | api.Field_View.prototype.initialize.apply( this ); 301 | 302 | this.listenTo( this.model, 'change:dragging', this.dragging ); 303 | this.listenTo( this.model, 'addEmptyField', this.addEmptyField ); 304 | this.listenTo( this.model.get( 'setting' ), 'remove', this.checkErrors ); 305 | this.listenTo( this.model.get( 'setting' ), 'add remove reset', this.updateSize ); 306 | this.on( 'render', this.updateSize ); 307 | }, 308 | /** 309 | * @memberOf Collection_View 310 | * @inheritDoc 311 | */ 312 | parseData: function( data ) { 313 | if ( _.size( data.blocks ) < 1 ) 314 | data.blocks = null; 315 | 316 | return data; 317 | }, 318 | /** 319 | * Add new Collection Fieldset 320 | * @memberOf Collection_View 321 | * @return {Collection_View} `this` 322 | */ 323 | add: function() { 324 | var added = this.model.get( 'setting' ).add(); 325 | 326 | if ( added == false ) { 327 | console.warn( 'Limit is exceeded!' ); 328 | this.showError( 'limit' ); 329 | 330 | return this; 331 | } 332 | 333 | if ( _.isArray( added ) ) { 334 | added = _.first( added ); 335 | } 336 | 337 | if ( added instanceof api.Collection_Fieldset ) { 338 | added.trigger( 'focus' ); 339 | } 340 | 341 | return this; 342 | }, 343 | toggleBlocks: function( event ) { 344 | if ( event ) { 345 | var $blocks; 346 | if ( $( event.target ).closest( '.ob-field__footer__actions' ).length ) { 347 | $blocks = $( 'div:not(.ob-field) .ob-field__footer__actions .choose .blocks', this.$el ); 348 | } else { 349 | $blocks = $( 'div:not(.ob-field) .ob-field__header__actions .choose .blocks', this.$el ); 350 | } 351 | 352 | if ( 'block' === $blocks.css( 'display' ) ) { 353 | $blocks.css( 'display', 'none' ); 354 | } else { 355 | $blocks.css( 'display', 'block' ); 356 | } 357 | } else { 358 | var $blocks = $( 'div:not(.ob-field) .choose .blocks', this.$el ); 359 | var is_showed = false; 360 | $blocks.each( function() { 361 | if ( 'block' === this.css( 'display' ) ) { 362 | is_showed = true; 363 | } 364 | } ); 365 | 366 | if ( is_showed ) { 367 | this.hideBlocks(); 368 | } else { 369 | this.showBlocks(); 370 | } 371 | } 372 | 373 | return this; 374 | }, 375 | showBlocks: function( event ) { 376 | if ( event ) { 377 | if ( $( event.target ).closest( '.ob-field__footer__actions' ).length ) { 378 | $( 'div:not(.ob-field) .ob-field__footer__actions .choose .blocks', this.$el ).css( 'display', 'block' ); 379 | } else { 380 | $( 'div:not(.ob-field) .ob-field__header__actions .choose .blocks', this.$el ).css( 'display', 'block' ); 381 | } 382 | } else { 383 | $( 'div:not(.ob-field) .choose .blocks', this.$el ).css( 'display', 'block' ); 384 | } 385 | }, 386 | hideBlocks: function( event ) { 387 | if ( event ) { 388 | event.stopPropagation(); 389 | } 390 | $( 'div:not(.ob-field) .choose .blocks', this.$el ).css( 'display', 'none' ); 391 | }, 392 | addBlock: function( event ) { 393 | event.stopPropagation(); 394 | var added = this.model.get( 'setting' ).add( { block: $( event.target ).data( 'block' ) } ); 395 | 396 | this.hideBlocks(); 397 | 398 | if ( added == false ) { 399 | console.warn( 'Limit is exceeded!' ); 400 | this.showError( 'limit' ); 401 | 402 | return this; 403 | } 404 | 405 | if ( _.isArray( added ) ) { 406 | added = _.first( added ); 407 | } 408 | 409 | if ( added instanceof api.Collection_Block ) { 410 | added.trigger( 'focus' ); 411 | } 412 | 413 | return this; 414 | }, 415 | /** 416 | * Update collection size info 417 | * @return {Collection_View} `this` 418 | */ 419 | updateSize: function() { 420 | var limit = this.model.get( 'limit' ); 421 | 422 | if ( ! _.isFinite( limit ) || limit < 0 ) return this; 423 | 424 | var size = this.model.size(); 425 | 426 | $( '> .numbers', this.$el ).css( 'display', 'block' ); 427 | $( '> .numbers .size', this.$el ).text( size ); 428 | $( '> .numbers .limit', this.$el ).text( limit ); 429 | 430 | return this; 431 | }, 432 | showError: function( error ) { 433 | $( '> .messages .error-' + error, this.$el ).css( 'display', 'block' ); 434 | 435 | return this; 436 | }, 437 | hideError: function( error ) { 438 | $( '> .messages .error-' + error, this.$el ).css( 'display', 'none' ); 439 | 440 | return this; 441 | }, 442 | checkErrors: function() { 443 | var limit = this.model.get( 'limit' ); 444 | if ( _.isFinite( limit ) && limit >= 0 ) { 445 | var size = this.model.size(); 446 | 447 | if ( size <= limit ) 448 | this.hideError( 'limit' ); 449 | } 450 | }, 451 | /** 452 | * Remove or add dragging class 453 | * @memberOf Collection_View 454 | */ 455 | dragging: function() { 456 | var dragging = this.model.get( 'dragging' ); 457 | if ( dragging === false ) 458 | this.$el.removeClass( 'dragging' ); 459 | else 460 | this.$el.addClass( 'dragging' ); 461 | }, 462 | /** 463 | * Add empty field to Collection 464 | * @memberOf Collection_View 465 | * @return {Collection_View} `this` 466 | */ 467 | addEmptyField: function() { 468 | var emptyField = $( '' ); 469 | this.$el.append( emptyField ); 470 | 471 | return this; 472 | } 473 | } ); 474 | 475 | /** 476 | * Collection Fieldset model 477 | * @class Collection_Fieldset 478 | * @extends {Field} 479 | */ 480 | api.Collection_Fieldset = api.Field.extend( { 481 | /** 482 | * Default options 483 | * @memberOf Collection_Fieldset 484 | * @type {Object} 485 | * @extends {Field.prototype.defaults} 486 | */ 487 | defaults: _.extend( {}, api.Field.prototype.defaults, { 488 | 'fields': {}, 489 | 'type': 'collection-fieldset', 490 | 'dragAllowed': false 491 | } ), 492 | /** 493 | * Initialize Collection Fieldset model 494 | * @memberOf Collection_Fieldset 495 | */ 496 | initialize: function() { 497 | this.add_fields( this.get( 'fields' ) ); 498 | this.on( 'move', this.move ); 499 | this.on( 'focus', this.focus ); 500 | }, 501 | /** 502 | * Add fields to Collection Fieldset 503 | * @memberOf Collection_Fieldset 504 | * @param {Object} data Fields informations 505 | * @return {Collection_Fieldset} `this` 506 | */ 507 | add_fields: function( data ) { 508 | this.set( 'fields', api.FieldConstructor( data, { parent: this } ) ); 509 | 510 | return this; 511 | }, 512 | /** 513 | * Return Field with given key as Field ID. This function does not go deeper of first childs. 514 | * @memberOf Collection_Fieldset 515 | * @param {String} key Field ID 516 | * @return {Object|Boolean} Return false if Field with that Field ID does not exist. 517 | */ 518 | get_field: function( key ) { 519 | if ( this.get( 'fields' )[key] != null ) return this.get( 'fields' )[key]; 520 | 521 | return false; 522 | }, 523 | /** 524 | * @memberOf Collection_Fieldset 525 | * @inheritDoc 526 | */ 527 | set_settings: function( data ) { 528 | for ( key in data ) { 529 | var field = this.get_field( key ) 530 | if ( field != false ) 531 | field.set_settings( data[key] ); 532 | } 533 | 534 | return this; 535 | }, 536 | /** 537 | * Collect values of child fields 538 | * @memberOf Collection_Fieldset 539 | */ 540 | collect_values: function() { 541 | var fields = this.get( 'fields' ); 542 | for ( key in fields ) { 543 | fields[key].collect_values(); 544 | } 545 | 546 | return this; 547 | }, 548 | /** 549 | * @memberOf Collection_Fieldset 550 | * @inheritDoc 551 | */ 552 | render: function() { 553 | if ( ! this.view ) { 554 | api.FieldViewConstructor( { model: this } ); 555 | this.listenTo( this.view, 'render', this.render_fields ); 556 | this.listenTo( this.view, 'remove', this.remove ); 557 | } 558 | 559 | this.trigger( 'render' ); 560 | 561 | return this; 562 | }, 563 | /** 564 | * Render Collection Fieldsets fields 565 | * @memberOf Collection_Fieldset 566 | * @return {Collection_Fieldset} `this` 567 | */ 568 | render_fields: function() { 569 | var fields = this.get( 'fields' ); 570 | for ( key in fields ) 571 | fields[key].render(); 572 | 573 | return this; 574 | }, 575 | /** 576 | * Remove this Collection Fieldset from parent Collection 577 | * @memberOf Collection_Fieldset 578 | * @return {Boolean} 579 | */ 580 | remove: function() { 581 | if ( this.get( 'parent' ) && this.get( 'parent' ).get( 'setting' ) ) { 582 | this.get( 'parent' ).get( 'setting' ).remove( this ); 583 | 584 | return true; 585 | } 586 | 587 | return false; 588 | }, 589 | /** 590 | * @memberOf Collection_Fieldset 591 | * @inheritDoc 592 | */ 593 | generate_names: function() { 594 | api.Field.prototype.generate_names.apply( this ); 595 | 596 | var fields = this.get( 'fields' ); 597 | for ( key in fields ) 598 | fields[key].generate_names(); 599 | 600 | return true; 601 | }, 602 | move: function( index ) { 603 | if ( ( this.collection.size() - 1 ) == index ) 604 | this.view.moveToEnd(); 605 | else 606 | this.view.moveBefore( this.collection.get( index + 1 ).view.$el ); 607 | 608 | return this; 609 | }, 610 | focus: function() { 611 | var fields = this.get( 'fields' ); 612 | for ( key in fields ) { 613 | if ( true === fields[key].get( 'can_be_focused' ) ) { 614 | fields[key].trigger( 'focus' ); 615 | break; 616 | } 617 | } 618 | 619 | return this; 620 | } 621 | } ); 622 | 623 | /** 624 | * Collection Fieldset view 625 | * @class Collection_Fieldset_View 626 | * @extends {Field_View} 627 | */ 628 | api.Collection_Fieldset_View = api.Field_View.extend( { 629 | /** 630 | * @memberOf Collection_Fieldset_View 631 | * @inheritDoc 632 | */ 633 | className: 'ob-field ob-collection-fieldset', 634 | /** 635 | * DOM Element attributes 636 | * @memberOf Collection_Fieldset_View 637 | * @type {Object} 638 | */ 639 | attributes: { 640 | 'draggable': true 641 | }, 642 | /** 643 | * Listen to events 644 | * @memberOf Collection_Fieldset_View 645 | * @type {Object} 646 | */ 647 | events: { 648 | 'dragstart': 'dragstart', 649 | 'dragenter': 'dragenter', 650 | 'dragleave': 'dragleave', 651 | 'dragend': 'dragend', 652 | 'dragover': 'dragover', 653 | 'drop': 'drop', 654 | 'mouseenter div:not(.ob-field) .drag-handler': 'allowDrag', 655 | 'mouseleave div:not(.ob-field) .drag-handler': 'disallowDrag', 656 | 'click div:not(.ob-field) .remove': 'remove' 657 | }, 658 | /** 659 | * @memberOf Collection_Fieldset_View 660 | * @inheritDoc 661 | */ 662 | template: wp.template( 'ob-collection-fieldset' ), 663 | /** 664 | * @inheritDoc 665 | */ 666 | initialize: function() { 667 | api.Field_View.prototype.initialize.apply( this ); 668 | 669 | this.listenTo( this.model, 'change:id', this.updateId ); 670 | }, 671 | /** 672 | * Replace old id with new id 673 | * @memberOf Field_View 674 | * @param {Object} model This model 675 | * @param {String} value New value 676 | * @param {Object} options 677 | */ 678 | updateId: function( model, value, options ) { 679 | var id = parseInt( value ) + 1; 680 | $( 'div:not(.ob-field) .drag-handler .id', this.$el ).text( id ); 681 | }, 682 | allowDrag: function() { 683 | this.model.set( 'dragAllowed', true ); 684 | 685 | return this; 686 | }, 687 | disallowDrag: function() { 688 | this.model.set( 'dragAllowed', false ); 689 | 690 | return this; 691 | }, 692 | /** 693 | * on dragstart event 694 | * @memberOf Collection_Fieldset_View 695 | * @param {Event} event 696 | */ 697 | dragstart: function( event ) { 698 | if ( this.model.get( 'dragAllowed' ) !== true ) { 699 | event.preventDefault(); 700 | return false; 701 | } 702 | 703 | event.originalEvent.dataTransfer.setData( 'text/plain', 'anything' ); 704 | event.stopPropagation(); 705 | 706 | var model = this.model; 707 | var parent = model.get( 'parent' ); 708 | 709 | parent.set( 'dragging', parent.get( 'setting' ).indexOf( model ) ); 710 | }, 711 | /** 712 | * on dragenter event 713 | * @memberOf Collection_Fieldset_View 714 | * @param {Event} event 715 | */ 716 | dragenter: function( event ) { 717 | if ( this.model.get( 'parent' ).get( 'dragging' ) !== false ) 718 | this.$el.addClass( 'over' ); 719 | }, 720 | /** 721 | * on dragleave event 722 | * @memberOf Collection_Fieldset_View 723 | * @param {Event} event 724 | */ 725 | dragleave: function( event ) { 726 | if ( this.model.get( 'parent' ).get( 'dragging' ) !== false ) 727 | this.$el.removeClass( 'over' ); 728 | }, 729 | /** 730 | * on dragend event 731 | * @memberOf Collection_Fieldset_View 732 | * @param {Event} event 733 | */ 734 | dragend: function( event ) { 735 | event.stopPropagation(); 736 | 737 | this.model.get( 'parent' ).set( 'dragging', false ); 738 | }, 739 | /** 740 | * on dragover event 741 | * @memberOf Collection_Fieldset_View 742 | * @param {Event} event 743 | */ 744 | dragover: function( event ) { 745 | event.preventDefault(); 746 | }, 747 | /** 748 | * on drop event 749 | * @memberOf Collection_Fieldset_View 750 | * @param {Event} event 751 | */ 752 | drop: function ( event ) { 753 | event.preventDefault(); 754 | event.stopPropagation(); 755 | var parent = this.model.get( 'parent' ); 756 | var setting = parent.get( 'setting' ); 757 | this.$el.removeClass( 'over' ); 758 | 759 | parent.move( setting.at( parent.get( 'dragging' ) ), setting.indexOf( this.model ) ); 760 | parent.set( 'dragging', false ); 761 | }, 762 | /** 763 | * Detach this Collection Fieldset DOM Element 764 | * @memberOf Collection_Fieldset_View 765 | * @return {Collection_Fieldset_View} `this` 766 | */ 767 | remove: function() { 768 | this.$el.detach(); 769 | this.trigger( 'remove' ); 770 | 771 | return this; 772 | }, 773 | moveToEnd: function() { 774 | this.model.trigger( 'before_move' ); 775 | 776 | this.$el.appendTo( this.model.get( 'parent' ).view.getFieldsEl() ); 777 | 778 | this.model.trigger( 'after_move' ); 779 | 780 | return this; 781 | }, 782 | moveBefore: function( el ) { 783 | this.model.trigger( 'before_move' ); 784 | 785 | this.$el.insertBefore( el ); 786 | 787 | this.model.trigger( 'after_move' ); 788 | 789 | return this; 790 | } 791 | } ); 792 | 793 | /** 794 | * Collection Block model 795 | * @class Collection_Block 796 | * @extends {Collection_Fieldset} 797 | */ 798 | api.Collection_Block = api.Collection_Fieldset.extend( { 799 | 800 | } ); 801 | 802 | /** 803 | * Collection Block view 804 | * @class Collection_Block_View 805 | * @extends {Collection_Fieldset_View} 806 | */ 807 | api.Collection_Block_View = api.Collection_Fieldset_View.extend( { 808 | /** 809 | * @memberOf Collection_Fieldset_View 810 | * @inheritDoc 811 | */ 812 | className: 'ob-field ob-collection-block', 813 | 814 | /** 815 | * @memberOf Collection_Fieldset_View 816 | * @inheritDoc 817 | */ 818 | template: wp.template( 'ob-collection-block' ) 819 | 820 | } ); 821 | 822 | /** 823 | * Extend Field prototype with new defaults, 'is_collection_child' method and change generate_names method 824 | */ 825 | _.extend( api.Field.prototype, { 826 | /** 827 | * Extend default options 828 | * @type {Object} 829 | */ 830 | defaults: _.extend( api.Field.prototype.defaults, { 831 | 'collection_child': null 832 | } ), 833 | events: { 834 | 'initialize': 'is_collection_child' 835 | }, 836 | /** 837 | * Returns true if Field is inside Collection 838 | * @memberOf Field 839 | * @return {Boolean} 840 | */ 841 | is_collection_child: function() { 842 | var collection_child = this.get( 'collection_child' ); 843 | if ( collection_child === true || collection_child === false ) return collection_child; 844 | 845 | if ( this.get( 'parent' ).get( 'collection_child' ) == true || this.get( 'parent' ) instanceof api.Collection || this.get( 'parent' ) instanceof api.Collection_Fieldset ) { 846 | this.set( 'collection_child', true, { silent: true } ); 847 | 848 | return true; 849 | } 850 | 851 | return false; 852 | }, 853 | /** 854 | * Generate Field name 855 | * @memberOf Field 856 | * @return {Field} `this` 857 | */ 858 | generate_names: function() { 859 | var 860 | id = this.get( 'id' ), 861 | is_collection_child = this.is_collection_child(), 862 | parent = this.get( 'parent' ); 863 | var name = ''; 864 | 865 | if ( parent ) 866 | name += parent.get( 'name' ); 867 | 868 | if ( ( id + '' ).length ) { 869 | if ( is_collection_child ) 870 | name += '['; 871 | else 872 | name += '_'; 873 | 874 | name += id; 875 | 876 | if ( is_collection_child ) 877 | name += ']'; 878 | } 879 | 880 | this.set( 'name', name ); 881 | 882 | return this; 883 | } 884 | } ); 885 | 886 | /** 887 | * Add Collection to OmniBuilder 888 | */ 889 | api.add( 'collection', { 890 | model: api.Collection, 891 | view: api.Collection_View 892 | } ); 893 | 894 | /** 895 | * Add Collection Fieldset to OmniBuilder 896 | */ 897 | api.add( 'collection-fieldset', { 898 | model: api.Collection_Fieldset, 899 | view: api.Collection_Fieldset_View 900 | } ); 901 | 902 | /** 903 | * Add Collection Block to OmniBuilder 904 | */ 905 | api.add( 'collection-block', { 906 | model: api.Collection_Block, 907 | view: api.Collection_Block_View 908 | } ); 909 | 910 | /** 911 | * Extend OmniBuilder API 912 | */ 913 | _.extend( exports.OB, api ); 914 | 915 | } ) ( wp, jQuery ) --------------------------------------------------------------------------------