';
// Get the appropriate markup
if (!this.formJsonToMarkup[type]){
console.log('ERROR: Your specified `input_type` of ', type, 'on the object', data, 'is missing a corresponding function in `formJsonToMarkup`');
}
markup += this.formJsonToMarkup[type].call(this, fieldName, data, isDefaultEvent, selectedVal, data.type);
markup += '
';
markup += '
';
}
return markup;
},
formJsonToMarkup: {
search: function(fieldName, data, isDefaultEvent){
// Rename the field name
fieldName = 'assignees-selector';
// var required = data.required ? 'required' : '';
// Give this a normal text input now, minus the data value, we'll instantiate the article searcher and load selecteds after it's been added to the DOM
var input_markup = '';
return input_markup;
},
content_items: function(assignees){
var markup = '';
assignees.forEach(function(assignee){
markup += this.assignmentTemplateFactory(assignee);
}, this);
return markup;
},
set_event_content_items: function(assignees){
return this.formJsonToMarkup.content_items.call(this, assignees);
},
datepicker: function(fieldName, data, isDefaultEvent){
var class_name = this.removeSetEventPrefix(fieldName);
var required = data.required ? 'required' : '';
// Give this a normal timestamp input, we'll instantiate pikaday after it's been added to the DOM
// This first input is for the pikaday select and display
var input_markup = '';
// Make an input sibling that will be what we read the data from
input_markup += '';
return input_markup;
},
textNumberOrHidden: function(fieldName, data, which, isDefaultEvent, selectedVal, serializeFn){
serializeFn = serializeFn || 'auto';
// TODO, investigate why value needs to be double escaped for Brian's version of Chrome.
var value = this.encodeQuotes(selectedVal),
class_name = this.removeSetEventPrefix(fieldName);
var required = data.required ? 'required' : '';
var input_markup = '';
return input_markup;
},
text: function(fieldName, data, isDefaultEvent, selectedVal){
return this.formJsonToMarkup.textNumberOrHidden.call(this, fieldName, data, 'text', isDefaultEvent, selectedVal);
},
hidden: function(fieldName, data, isDefaultEvent, selectedVal){
return this.formJsonToMarkup.textNumberOrHidden.call(this, fieldName, data, 'hidden', isDefaultEvent, selectedVal);
},
searchstring: function(fieldName, data, isDefaultEvent, selectedVal){
// Do the same as text
return this.formJsonToMarkup.text.call(this, fieldName, data, isDefaultEvent, selectedVal);
},
'string-to-list': function(fieldName, data, isDefaultEvent, selectedVal){
// Do the same as text
return this.formJsonToMarkup.textNumberOrHidden.call(this, fieldName, data, 'text', isDefaultEvent, selectedVal, 'string-to-list');
},
number: function(fieldName, data, isDefaultEvent, selectedVal){
return this.formJsonToMarkup.textNumberOrHidden.call(this, fieldName, data, 'number', isDefaultEvent, selectedVal);
},
paragraph: function(fieldName, data, isDefaultEvent, selectedVal){
var value = this.encodeQuotes(selectedVal) || '',
class_name = this.removeSetEventPrefix(fieldName);
var input_markup = '';
return input_markup;
},
select: function(fieldName, data, isDefaultEvent, selectedVal){
var class_name = this.removeSetEventPrefix(fieldName);
var required = data.required ? 'required' : '';
var input_markup = '';
return input_markup;
},
'checkbox-single': function(fieldName, data, isDefaultEvent, selectedVal, type){
var input_markup = '',
namespacer = 'NewsLynx'; // To avoid id collisions
data.input_options.forEach(function(checkboxItemObj, idx){
var checkboxId = _.uniqueId(namespacer + '|' + fieldName + '|'); // `form.serializeArray()` will turn this into a data value so later on we'll to remove the number from this value since it will needed to become a generic property name on the key.
var checked = (selectedVal) ? 'checked' : '';
input_markup += '';
input_markup += '';
})
return input_markup
},
checkbox: function(fieldName, data, isDefaultEvent, selectedVal, type){
var input_markup = '',
namespacer = 'NewsLynx'; // To avoid id collisions
if (!data.input_options.length){
input_markup = 'You haven\'t yet made any '+fieldName.replace(/^set_event_/,'').replace(/_/g,' ')+' yet. Create them on the Settings page.';
}
// var len = data.input_options.length;
_.each(data.input_options, function(checkboxItemObj, idx){
var style = this.styleCheckboxLabel(checkboxItemObj);
var checkboxId = _.uniqueId(namespacer+'|'+fieldName + '|' + checkboxItemObj.id + '|'); // `form.serializeArray()` will turn this into a data value so later on we'll to remove the number from this value since it will needed to become a generic property name on the key.
input_markup += '
';
var checked = '';
var selected_ids = selectedVal;
if (_.contains(selected_ids, checkboxItemObj.id)) {
checked = 'checked';
}
var tooltipped = (type !== 'subject') ? 'tooltipped' : '';
// var label_orientation = (idx != (len - 1)) ? '' : 'right';
input_markup += '';
input_markup += '';
input_markup += '
';
}, this);
return input_markup;
}
},
styleCheckboxLabel: function(checkboxItemObj){
// console.log(checkboxItemObj, checkboxItemObj.color)
var bgColor = checkboxItemObj.color,
color = this.whiteOrBlack(bgColor);
return 'style="background-color:'+bgColor+';color:'+color+';"';
},
prettyName: function(name, type){
// Standardize name to remove the `set_event_` prefix used in keys on default event creator forms like when creating or modifying a recipe
if (/^set_event_/.test(name)){
name = name.replace('set_event_', '');
}
if (name === 'tag_ids' && type === 'subject'){
name = 'subject_tag_ids';
}
// Make any name changes here to prettify things that might not be terribly evident what they do from their API slug.
var name_changes = {
'q': 'search_query',
'content_items': 'Assign to..',
'url': 'URL',
'img_url': 'Image URL',
'filter': 'search_query',
'min_followers': 'min. followers',
'tag_ids': 'impact tag(s)',
'subject_tag_ids': 'Subject tag(s)',
'datetime': 'date / time',
'create': 'date / time',
'interval': 'interval (seconds)',
'title': 'event title'
};
if (name_changes[name]) {
name = name_changes[name];
}
if (!name) {
name = '';
}
name = name.replace(/_/g, ' ');
return name.charAt(0).toUpperCase() + name.slice(1);
},
escapeQuotes: function(term){
return term.replace(/"/g, '\\"').replace(/'/g, "\\'")
},
encodeQuotes: function(term){
if (term === 0) { return term; }
if (!term) { return ''; }
if (typeof term !== 'string') { return term };
return term.replace(/"/g,'"').replace(/'/g,''')
},
prettyDatestamp: function(utcDate){
return new Date(utcDate).toLocaleString();
},
initArticleTitleSearcher: function(){
var $typeahead = this.$el.find('.assignees-selector'),
self = this;
// TEMPORARY until we fully eliminate `combineForm` stuff
// For now, mimic the structure it's expecting
// if (!content_items && this.form_info.vals){
// content_items = {
// selected: this.form_info.vals.content_items
// }
// }
this.$typeaheadRow = $typeahead.parents('.form-row');
$('').insertAfter(this.$typeaheadRow);
$typeahead.typeahead({
highlight: true,
minLength: 3
},{
name: 'content-items',
displayKey: 'title',
async: true,
source: function(query, syncResults, asyncResults){
var query_url = '/api/_VERSION/content?q='+query+'&search=title&fields=id,title';
$.ajax({
url: query_url,
dataType: 'json',
success: function(results){
asyncResults(results.content_items);
}
});
}
});
$typeahead.on('typeahead:selected', function(e, d){
e.preventDefault();
// Clear this val on selection
$(this).typeahead('val', '');
// Add selection down below
self.addArticleAssignee(d, content_items);
});
// Load the selecteds, if they exist
var content_items;
if (this.form_info && this.form_info.vals.content_items){
this.form_info.vals.content_items.forEach(function(contentItem){
self.addArticleAssignee(contentItem, []);
});
}
return this;
},
addArticleAssignee: function(newContentItem, existingContentItems){
var $articleAssignments,
id,
form_data,
markup;
if (newContentItem){
$articleAssignments = this.$el.find('.form-row.article-assignees');
id = newContentItem.id;
var form_data = this.getSettings(true); // `true` means get the default event values also
// Currently, selecting an article won't remove it from the typeahead options, so as a fix, don't add things if they already exist
if (!_.contains(_.pluck(existingContentItems, 'id'), id)){
_.extend(newContentItem, {encodeQuotes: this.encodeQuotes})
markup = this.assignmentTemplateFactory(newContentItem);
$articleAssignments.append(markup);
}
} else {
console.log('ERROR: Article assignee not found');
}
return this;
},
removeArticleAssignee: function(e){
// Remove from DOM
$(e.currentTarget).remove();
return this;
},
initPikaday: function(){
// TODO, future plan, refactor this function so that it could could detect multiple datepicker values and init them.
var time_picker,
that = this,
$el = this.$el.find('input[data-type="datepicker"]'), // This convention of using `data-type` breaks from our current convention of using names. But those names follow data key fields. In this case, `created`, is a bit too specific. This gives us flexibility in making text fields Pikaday instances without them being tied to a specific datakey. see `formJsonToMarkup.datepicker` for where this is set.
el = $el[0], // Pikaday wants a pure dom object, not a jquery object
$form_el = $el.siblings('input[data-type="datepicker-value"]'); // We create a sibling value that stores the properly formatted date string so we can use the $el input for a prettier display;
time_picker = new Pikaday({
field: el,
showTime: true,
use24hour: true,
timezone: pageData.timezone,
// clearInvalidInput: true,
// onOpen: function(){
// // console.log('existing',this.toString())
// },
onSelect: function(){
var moment_timezone_date = this.getMoment(),
full_date_string = moment_timezone_date.format(),
pretty_date_string = moment_timezone_date.format(helpers.templates.prettyDateTimeFormat); // June 23, 2014, 9:13 am
$form_el.val(full_date_string);
$el.val(pretty_date_string);
// console.log('form_el',full_date_string)
// console.log('el',pretty_date_string)
}
// ,
// onClear: function(){
// // // Clear the timestamp on error or non-date value
// // that.form_data.timestamp = null;
// }
});
// var date_obj_in_schema = _.findWhere(this.form_info.schema, {input_type: 'datepicker'}) || {}; // Give a `{}` if undefined so the next line will fail gracefully
// var datepicker_key_name = Object.keys(date_obj_in_schema)[0];
// var selected_date = this.form_info.vals[datepicker_key_name];
var selected_date;
// This won't always have a value, if we're initing pikaday without preloading
if (this.form_info){
selected_date = moment(this.form_info.vals.created);
time_picker.setMoment(selected_date);
}
this._time_picker = time_picker;
return this;
},
whiteOrBlack: function(bgColorHex){
var rgbColor = this.hexToRgb(bgColorHex);
var r = rgbColor.r,
g = rgbColor.g,
b = rgbColor.b;
var yiq = (r * 299 + g * 587 + b * 114) / 1000;
return (yiq >= 128) ? 'black' : 'white';
},
hexToRgb: function(hex){
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
},
bakeButtons: function(includeDelete){
var markup = '
';
// Cancel
markup += '';
// Save
markup += '';
markup += ''
// Delete (optional)
if (includeDelete){
markup += '';
}
// Close button container
markup += '
';
return markup;
},
validate: function(schema, formData, cb, ctx){
var required_keys = [];
_.each(schema, function(val, key){
if (val.required){
required_keys.push(key);
}
});
var existing_keys = _.keys(formData),
existing_vals = _.values(formData),
msg,
missing_keys = [], // We'll do a few tests and push the missing keys into here and then flatten
s = '';
// Test if we flat out missed some
missing_keys.push(_.difference(required_keys, existing_keys));
// Test if some are empty by testing for null and length. This will break on a number so do some type testing first
_.each(formData, function(existingVal, existingKey){
if (existingVal === undefined || existingVal === false || existingVal === ''){
missing_keys = missing_keys.concat(existingKey);
}
});
// Flatten this weird array structure we've made
missing_keys = _.flatten(missing_keys);
// And we're only interested in required ones
missing_keys = _.intersection(required_keys, missing_keys);
if (missing_keys.length > 0){
if (missing_keys.length > 1){
s = 's';
}
missing_keys = missing_keys.map(function(missingKey){
return '"' + helpers.templates.toTitleCase(missingKey).replace(/_/g, ' ') + '"';
}).join(', ');
msg = 'You didn\'t include information for the required field' + s + ': ' + missing_keys + '.'
cb.call(ctx, 'Missing fields', msg);
} else {
cb.call(ctx, null, 'Saving!');
}
},
printMsgOnSubmit: function(error, msg){
var class_name = (error) ? 'fail' : 'success';
this.$submitMsgText.removeClass('success').removeClass('fail');
this.$submitMsgText.addClass(class_name).html(msg);
},
getSettings: function(setDefaultEvent){
// Skip any that we've decided are skippable
var $form_selector = this.$el.find('form :input[data-serialize-skip!="true"]').filter(function(){
var $this = $(this);
// Only take inputs that have values, have values that are `0`, or are a part of `set_event_`, which we're okay being null.
return $this.val() || $this.val() === 0 || /^set_event_/.test($this.attr('name'));
});
// If we don't want to set default event options
// Only include the non-default-evnt input fields in our serializer when creating the json
if (!setDefaultEvent){
$form_selector = $form_selector.filter(function(){
return !$(this).attr('data-is-default-event') || $(this).attr('data-is-default-event') == 'false';
});
}
var form_options = $form_selector.serializeJSON({
checkboxUncheckedValue: true,
customTypes: {
// Adapted from serializejson source
'reverse-boolean': function(str) {
var falses = ["false", "null", "undefined", "", "0"];
return falses.indexOf(str) !== -1;
},
// Split on comma and trim white space
'string-to-list': function(val){
return val.split(',').map(function(str){
return str.trim();
})
},
'object-single-quotes': function(val){
return JSON.parse(val.replace(/'/g, '"'))
}
}
});
return form_options;
},
drag: function(){
return d3.behavior.drag()
.on('drag', function(d,i,e) {
// FF has a bug where it will allow for drag when the mouse is selected text in an input field
// This means that if you try to click and select text, it will move the window
// Same thing for other browser on the scrollbar of the impact tags
// As a fix, disable all dragging if you're within an input child
if (this.canDrag){
var D3_modal_inner = d3.select(this).select('.modal-inner'),
top = parseInt(D3_modal_inner.style('top')),
left = parseInt(D3_modal_inner.style('left'));
top += d3.event.dy;
left += d3.event.dx;
D3_modal_inner.style('top', top+'px').style('left', left+'px');
}
})
.on('dragstart', function(){
var elements_to_not_drag = ['.form-row-input-container'],
$dragging_element = $(d3.event.sourceEvent.explicitOriginalTarget);
var can_drag = _.every(elements_to_not_drag, function(elementToDrag){
if ($dragging_element.hasClass(elementToDrag.replace('.',''))){
return false;
} else if ($dragging_element.parents(elementToDrag).length > 0){
return false;
} else {
return true;
}
});
this.canDrag = can_drag;
})
},
flashSubmitMsg: function(error, msg){
var class_name = 'success';
if (error) class_name = 'fail';
this.$submitMsgText.removeClass('success').removeClass('fail');
// Fade out message, then make sure it's visible for the next time
this.$submitMsgText.addClass(class_name).html(msg).delay(7000).fadeOut(500).delay(750)
.queue(function(next) {
$(this).html('').removeClass(class_name).show();
next();
})
},
toggleBtnsDisabled: function(){
this.$form.find('.buttons-container').toggleClass('disabled');
}
});