CKEditor append child error - javascript

With the project I am on, I have been asked to move from CKEditor 3 to 4. Everything works fine except for a custom plugin someone wrote.
Argument 1 of Node.appendChild is not an object.
The code is all over the place, and a bit of a mess. Here is the plugin, which I think is causing the error.
/*
Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.html or http://ckeditor.com/license
*/
/**
* #fileOverview The "limereplacementfields" plugin.
*
*/
(function()
{
var limereplacementfieldsReplaceRegex = /(\{[A-Z]+[^\{\}]+[A-Z0-9]+\})/g;
CKEDITOR.plugins.add( 'limereplacementfields',
{
requires : [ 'dialog' ],
lang : [ 'en' ],
init : function( editor )
{
var lang = editor.lang.limereplacementfields;
editor.addCommand( 'createlimereplacementfields', new CKEDITOR.dialogCommand( 'createlimereplacementfields' ) );
editor.addCommand( 'editlimereplacementfields', new CKEDITOR.dialogCommand( 'editlimereplacementfields' ) );
editor.ui.addButton( 'Createlimereplacementfields',
{
label : lang.title,
command :'createlimereplacementfields',
icon : this.path + 'limereplacementfields.gif'
});
if ( editor.addMenuItems )
{
editor.addMenuGroup( 'limereplacementfields', 20 );
editor.addMenuItems(
{
editlimereplacementfields :
{
label : lang.title,
command : 'editlimereplacementfields',
group : 'limereplacementfields',
order : 1,
icon : this.path + 'limereplacementfields.gif'
}
} );
if ( editor.contextMenu )
{
editor.contextMenu.addListener( function( element, selection )
{
if ( !element || !element.data( 'cke-limereplacementfields' ) )
return null;
return { editlimereplacementfields : CKEDITOR.TRISTATE_OFF };
} );
}
}
editor.on( 'doubleclick', function( evt )
{
if ( CKEDITOR.plugins.limereplacementfields.getSelectedPlaceHoder( editor ) )
evt.data.dialog = 'editlimereplacementfields';
});
editor.on( 'contentDom', function()
{
editor.document.getBody().on( 'resizestart', function( evt )
{
if ( editor.getSelection().getSelectedElement().data( 'cke-limereplacementfields' ) )
evt.data.preventDefault();
});
});
CKEDITOR.dialog.add( 'createlimereplacementfields', this.path + 'dialogs/limereplacementfields.js' );
CKEDITOR.dialog.add( 'editlimereplacementfields', this.path + 'dialogs/limereplacementfields.js' );
},
afterInit : function( editor )
{
var dataProcessor = editor.dataProcessor,
dataFilter = dataProcessor && dataProcessor.dataFilter,
htmlFilter = dataProcessor && dataProcessor.htmlFilter;
if ( dataFilter )
{
dataFilter.addRules(
{
text : function( text )
{
return text.replace( limereplacementfieldsReplaceRegex, function( match )
{
return CKEDITOR.plugins.limereplacementfields.createlimereplacementfields( editor, null, match, 1 );
});
}
});
}
if ( htmlFilter )
{
htmlFilter.addRules(
{
elements :
{
'span' : function( element )
{
if ( element.attributes && element.attributes[ 'data-cke-limereplacementfields' ] )
delete element.name;
}
}
});
}
},
onLoad: function(editor) {
CKEDITOR.addCss(
'.cke_limereplacementfields' +
'{' +
'background-color: #ffff00;' +
( CKEDITOR.env.gecko ? 'cursor: default;' : '' ) +
'}'
);
}
});
})();
CKEDITOR.plugins.setLang('limereplacementfields','en', {
limereplacementfields: {
title:sReplacementFieldTitle,
button:sReplacementFieldButton
}
}
);
CKEDITOR.plugins.limereplacementfields =
{
createlimereplacementfields : function( editor, oldElement, text, isGet )
{
var element = new CKEDITOR.dom.element( 'span', editor.document );
element.setAttributes(
{
contentEditable : 'false',
'data-cke-limereplacementfields' : 1,
'class' : 'cke_limereplacementfields'
}
);
text && element.setText( text );
if ( isGet )
return element.getOuterHtml();
if ( oldElement )
{
if ( CKEDITOR.env.ie )
{
element.insertAfter( oldElement );
// Some time is required for IE before the element is removed.
setTimeout( function()
{
oldElement.remove();
element.focus();
}, 10 );
}
else
element.replace( oldElement );
}
else
editor.insertElement( element );
return null;
},
getSelectedPlaceHoder : function( editor )
{
var range = editor.getSelection().getRanges()[0];
range.shrink( CKEDITOR.SHRINK_TEXT );
var node = range.startContainer;
while( node && !( node.type == CKEDITOR.NODE_ELEMENT && node.data( 'cke-limereplacementfields' ) ) )
node = node.getParent();
return node;
}
};

Turns out it was another file altogether, which I had to rewrite.

Related

Mongoose custom Schema/Type with virtual populate works with Model.populate but not Document.populate

In the vein of mongoose-uuid2, I am trying to create a custom SchemaType for replacing the regular _id with UUID types. The goal is to create not just a SchemaType, but a related Type class as well, and mimic how ObjectId is actually a type. So I've implemented a SchemaType and Type class, as recommended, and overrode things like toString, toObject, and toBSON so that the Type class will return the underlying Buffer.Binary object that gets saved to the MongoDB for native operations.
All is good EXCEPT when doing populates. Mongoose use String(id) to relate the _id of the fetched documents to their linked parent document. So that's why I override the toString function to return String([underlying buffer]) and that fixed Model.populate but not Document.populate.
Let's get to the code. Here is the large not-so-minimal example:
const config = require( './lib/config' );
const mongoose = require( 'mongoose' );
const bson = require( 'bson' );
const util = require( 'util' );
const uuidParse = require( 'uuid-parse' );
const uuidv4 = require( 'uuid/v4' );
/**
* Convert a buffer to a UUID string
*/
stringFromBuffer = function( buf, len ) {
var hex = '';
if( len==null )
len = buf.length;
for (var i = 0; i < len; i++) {
var n = buf.readUInt8(i);
if (n < 16){
hex += '0' + n.toString(16);
} else {
hex += n.toString(16);
}
}
const s = hex.substr(0, 8) + '-' + hex.substr(8, 4) + '-' + hex.substr(12, 4) + '-' + hex.substr(16, 4) + '-' + hex.substr(20, 12);
//console.log( "getter returning hex" , s );
return( s );
}
/**
* Our helper TypeUUID class
*/
class TypeUUID {
/**
* Construct
*/
constructor( value ) {
// Set our type for checks
this.isMongooseUUID = true;
// Set our internal value
this.buffer = TypeUUID.convertToBuffer( value );
}
/**
* To BSON
*/
toBSON() {
if( this.buffer==null ) {
console.log( "toBSON returning null" )
return( null );
}
const r = this.buffer.toBSON();
console.log( "toBSON returning buffer converted.", r );
return( r );
}
/**
* To object
*/
toObject( options ) {
if( this.buffer==null ) {
console.log( "toObject returning null" );
return( null );
}
const r = this.buffer.toObject( options );
console.log( "toObject returning buffer converted.", r );
return( r );
}
/**
* The equals
*/
equals( other ) {
if( this.buffer==other )
return( true );
return( this.buffer.equals( TypeUUID.convertToBuffer( other ) ) );
}
/**
* Generate it
*/
static generate() {
// Generate
return( new TypeUUID( uuidv4() ) );
}
/**
* Convert the value from whatever it is
*/
static convertToBuffer( value ) {
let r;
// To buffer
if( value instanceof TypeUUID )
r = new mongoose.Types.Buffer( value.value );
else if( value==null )
return( null );
else if( value instanceof mongoose.Types.Buffer.Binary )
r = new mongoose.Types.Buffer( value.buffer );
else if( typeof( value )=== 'string' )
r = new mongoose.Types.Buffer( uuidParse.parse( value ) );
else if( value instanceof mongoose.Types.Buffer )
r = new mongoose.Types.Buffer( value );
else // How did this happen?
throw new Error( 'Could not cast ' + value + ' to UUID.' );
// Set the correct subtype
r.subtype( bson.Binary.SUBTYPE_UUID );
// Return it
return( r );
}
/**
* Stringy
*/
toString() {
if( this.buffer==null )
return( null );
//return( stringFromBuffer( this.buffer ) );
// The above will break Model.populate
return( String( this.buffer ) );
}
/**
* To JSON
*/
toJSON() {
return( this.valueOf() );
}
/**
* Value of
*/
valueOf() {
return( stringFromBuffer( this.buffer ) );
//return( String( this.buffer ) );
//return( this.toString() );
}
/**
* Convenience function
*/
static from( value ) {
return( new TypeUUID( value ) );
}
}
/**
* Schema type
*/
function SchemaUUID( path, options ) {
mongoose.SchemaTypes.Buffer.call( this, path, options );
}
SchemaUUID.get = mongoose.SchemaType.get;
SchemaUUID.set = mongoose.SchemaType.set;
util.inherits( SchemaUUID, mongoose.SchemaTypes.Buffer );
SchemaUUID.schemaName = 'UUID';
SchemaUUID.prototype.checkRequired = function( value ) {
console.log( "checkRequired", value );
// Either one
return( value && value.isMongooseUUID || value instanceof mongoose.Types.Buffer.Binary );
};
SchemaUUID.prototype.cast = function( value, doc, init ) {
// Nulls and undefineds aren't helpfill
if( value==null )
return( value );
// Is it a mongoose UUID dingy?
if( value.isMongooseUUID )
return( value );
// Is it already a binary?
if( value instanceof mongoose.Types.Buffer.Binary )
return( TypeUUID.from( value ) );
// It's a UUID string?
if( typeof( value )==='string' )
return( TypeUUID.from( value ) );
// Does this helpful?
if( value._id )
return( value._id );
throw new Error('Could not cast ' + value + ' to UUID.');
};
SchemaUUID.prototype.castForQuery = function( $conditional, val ) {
console.log( "castForQuery", $conditional, val );
var handler;
if (arguments.length === 2) {
handler = this.$conditionalHandlers[$conditional];
console.log( "Got handler", handler );
if (!handler) {
throw new Error("Can't use " + $conditional + " with UUID.");
}
return handler.call(this, val);
}
return( this.cast( $conditional ) )
};
// Add them to mongoose
mongoose.Types.UUID = TypeUUID;
mongoose.SchemaTypes.UUID = SchemaUUID;
// Do what the warnings say
mongoose.set( 'useNewUrlParser', true );
mongoose.set( 'useUnifiedTopology', true );
// Connect
{
const {
user,
password,
servers,
database,
authSource,
ssl
} = config.mongoose;
mongoose.connect( `mongodb://${user}:${password}#${servers.join(',')}/${database}?authSource=${authSource}&ssl=${ssl?"true":"false"}` );
}
const db = mongoose.connection
.on( 'error', console.log )
.on( 'open', ()=>console.log( "MongoDB connexion opened." ) );
// Shorthand to allow lazy
const { Schema, Types } = mongoose;
/**
* Current texting code pair
*/
const Aschema = new Schema( {
'_id' : { 'type' : Schema.Types.UUID, 'default' : Types.UUID.generate }, // Our ID
'singleB' : { 'type' : Schema.Types.UUID, 'ref' : "B" },
'multiB' : [ { 'type' : Schema.Types.UUID, 'ref' : "B" } ],
} );
const A = mongoose.model( "A", Aschema );
Aschema.virtual( "multiC", {
'ref' : "C",
'foreignField' : "aID",
'localField' : "_id",
'justOne' : false
} );
const B = mongoose.model( "B", new Schema( {
'_id' : { 'type' : Schema.Types.UUID, 'default' : Types.UUID.generate }, // Our ID
'name' : String
} ) );
const C = mongoose.model( "C", new Schema( {
'_id' : { 'type' : Schema.Types.UUID, 'default' : Types.UUID.generate }, // Our ID
'name' : String,
'aID' : { 'type' : Schema.Types.UUID, 'ref' : "A" }
} ) );
// Do testing here
(async function(){
try {
// Insert B's
const bs = await Promise.all( [ new B( { 'name' : "Beam" } ).save(), new B( { 'name' : "Truth" } ).save() ] );
console.log( "Our B's.", bs );
// Now insert A with B's.
let a = await new A( {
'singleB' : bs[ 0 ],
'multiB' : bs
} ).save();
// Check both
console.log( "Our A", a );
// Insert C's
const cs = await Promise.all( [
new C( { 'name' : "Got", 'aID' : a } ).save(),
new C( { 'name' : "Milk", 'aID' : a } ).save()
] );
//console.log( "C's are", await C.find( {} ).exec() );
// Fetch A using Model.populate
//a = await A.findOne( {} ).populate( "multiC" ).exec();
a = await A.findOne( {} ).populate( "singleB" ).populate( "multiB" ).populate( "multiC" ).exec();
// WORKS!
console.log( "Fetched A", a );
// Now fetch using document populate
a = await A.findOne( {} ).exec();
console.log( "Unpopulated A", a );
//await a.populate( "multiC" ).execPopulate();
await a.populate( "singleB" ).populate( "multiB" ).populate( "multiC" ).execPopulate();
// Does not work
console.log( "Populated A", a );
// Clear test
await A.deleteMany( {} ).exec();
await B.deleteMany( {} ).exec();
await C.deleteMany( {} ).exec();
}
catch( e ) {
console.error( "Why did we get here?", e );
}
// Exit not hang
process.exit( 0 );
})();
Now here is the output (with some debug console.logs redacted):
Our B's. [ { name: 'Beam',
_id: '1f6f47ac-c454-4495-9fc1-f8d5041648d6',
__v: 0 },
{ name: 'Truth',
_id: '35d79f47-9706-4b31-812b-dd50c79fa3fa',
__v: 0 } ]
Our A { multiB:
[ '1f6f47ac-c454-4495-9fc1-f8d5041648d6',
'35d79f47-9706-4b31-812b-dd50c79fa3fa' ],
singleB: '1f6f47ac-c454-4495-9fc1-f8d5041648d6',
_id: '0be17cc0-9f01-40c4-b344-edd8ef11d761',
__v: 0 }
Fetched A { multiB:
[ { _id: '1f6f47ac-c454-4495-9fc1-f8d5041648d6',
name: 'Beam',
__v: 0 },
{ _id: '35d79f47-9706-4b31-812b-dd50c79fa3fa',
name: 'Truth',
__v: 0 } ],
_id: '0be17cc0-9f01-40c4-b344-edd8ef11d761',
singleB:
{ _id: '1f6f47ac-c454-4495-9fc1-f8d5041648d6',
name: 'Beam',
__v: 0 },
__v: 0,
multiC:
[ { _id: '4d61aa05-dea8-4f57-9835-b6b0e6d38d92',
name: 'Got',
aID: '0be17cc0-9f01-40c4-b344-edd8ef11d761',
__v: 0 },
{ _id: '276ffc29-1f56-412e-9adb-9ab32069bd20',
name: 'Milk',
aID: '0be17cc0-9f01-40c4-b344-edd8ef11d761',
__v: 0 } ] }
Unpopulated A { multiB:
[ '1f6f47ac-c454-4495-9fc1-f8d5041648d6',
'35d79f47-9706-4b31-812b-dd50c79fa3fa' ],
_id: '0be17cc0-9f01-40c4-b344-edd8ef11d761',
singleB: '1f6f47ac-c454-4495-9fc1-f8d5041648d6',
__v: 0 }
Populated A { multiB:
[ { _id: '1f6f47ac-c454-4495-9fc1-f8d5041648d6',
name: 'Beam',
__v: 0 },
{ _id: '35d79f47-9706-4b31-812b-dd50c79fa3fa',
name: 'Truth',
__v: 0 } ],
_id: '0be17cc0-9f01-40c4-b344-edd8ef11d761',
singleB:
{ _id: '1f6f47ac-c454-4495-9fc1-f8d5041648d6',
name: 'Beam',
__v: 0 },
__v: 0 }
I can see a few things. The non-virtual singleB and multiB relations work no problem when doing Document.populate. The multiC virtual relation fails with multiC in the case of Document.populate but succeeds in Model.populate.
How can I fix it so that all cases will work as expected?
Bonus! I'd love if the toString function would return a UUID and not the raw binary value. Would that ever be a possibility?

Knockout: autocomplete on combobox binding not clearing the observable

I have an autocomplete combobox using both knockout and jquery ui libraries.
When you type something in that is not in the autocomplete list, the combobox gets blank and that is exactly what I want it to do, but it does not update my selectedOption value too (I would like to set it to null when the combobox is left blank)
I have tried adding a subscribe event to the selectedOption but knockout does not fire it in this case. I also tried with the valueAllowUnset property but it did not work either.
Thank you very much for your help!
Here is my snippet (it looks a bit ugly because I did not add CSS but it shows the problem):
function viewModel() {
var self = this;
self.myOptions = ko.observableArray([{
Name: "First option",
Id: 1
},
{
Name: "Second option",
Id: 2
},
{
Name: "Third option",
Id: 3
}
]);
self.selectedOption = ko.observable(3);
}
var myViewModel = new viewModel();
ko.applyBindings(myViewModel);
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.1.0/knockout-min.js"></script>
<html>
<body>
<p> Combobox: </p>
<select id="myCombo" data-bind="options: myOptions, optionsText: 'Name', optionsValue: 'Id', value: selectedOption, valueAllowUnset: true, combo: selectedOption"></select>
<p> Option selected Id: </p>
<texarea data-bind="text: selectedOption()"> </texarea>
</body>
</html>
<script>
// ko-combo.js
(function() {
//combobox
ko.bindingHandlers.combo = {
init: function(element, valueAccessor, allBindingsAccessor) {
//initialize combobox with some optional options
var options = {};
$(element).combobox({
select: function() {
var observable = valueAccessor();
observable($(element).combobox('value'));
}
});
//handle the field changing
ko.utils.registerEventHandler(element, "change", function() {
var observable = valueAccessor();
observable($(element).val());
});
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).combobox("destroy");
});
},
//update the control when the view model changes
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
$(element).combobox('value', value);
}
};
})();
</script>
<script>
// jquery.ui.combobox.js
/*!
* Copyright Ben Olson (https://github.com/bseth99/jquery-ui-extensions)
* jQuery UI ComboBox #VERSION
*
* Adapted from Jörn Zaefferer original implementation at
* http://www.learningjquery.com/2010/06/a-jquery-ui-combobox-under-the-hood
*
* And the demo at
* http://jqueryui.com/autocomplete/#combobox
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
(function( $, undefined ) {
$.widget( "ui.combobox", {
options: {
editable: false
},
version: "#VERSION",
widgetEventPrefix: "combobox",
uiCombo: null,
uiInput: null,
_wasOpen: false,
_create: function() {
var self = this,
select = this.element.hide(),
input, wrapper;
input = this.uiInput =
$( "<input />" )
.insertAfter(select)
.addClass("ui-widget ui-widget-content ui-corner-left ui-combobox-input")
.val( select.children(':selected').text() );
wrapper = this.uiCombo =
input.wrap( '<span>' )
.parent()
.addClass( 'ui-combobox' )
.insertAfter( select );
input
.autocomplete({
delay: 0,
minLength: 0,
appendTo: wrapper,
source: $.proxy( this, "_linkSelectList" )
});
$( "<button>" )
.attr( "tabIndex", -1 )
.attr( "type", "button" )
.insertAfter( input )
.button({
icons: {
primary: "ui-icon-triangle-1-s"
},
text: false
})
.removeClass( "ui-corner-all" )
.addClass( "ui-corner-right ui-button-icon ui-combobox-button" );
// Our items have HTML tags. The default rendering uses text()
// to set the content of the <a> tag. We need html().
input.data( "ui-autocomplete" )._renderItem = function( ul, item ) {
return $( "<li>" )
.append( $( "<a>" ).html( item.label ) )
.appendTo( ul );
};
this._on( this._events );
},
_linkSelectList: function( request, response ) {
var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), 'i' );
response( this.element.children('option').map(function() {
var text = $( this ).text();
if ( this.value && ( !request.term || matcher.test(text) ) ) {
return {
label: text.replace(
new RegExp(
"(?![^&;]+;)(?!<[^<>]*)(" +
$.ui.autocomplete.escapeRegex(request.term) +
")(?![^<>]*>)(?![^&;]+;)", "gi"),
"<strong>$1</strong>"),
value: text,
option: this
};
}
})
);
},
_events: {
"autocompletechange input" : function(event, ui) {
var $el = $(event.currentTarget);
if ( !ui.item ) {
var matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( $el.val() ) + "$", "i" ),
valid = false;
this.element.children( "option" ).each(function() {
if ( $( this ).text().match( matcher ) ) {
this.selected = valid = true;
return false;
}
});
if (!this.options.editable) {
if (!valid) {
// remove invalid value, as it didn't match anything
$el.val("");
this.element.prop('selectedIndex', -1);
//return false;
}
}
}
this._trigger( "change", event, {
item: ui.item ? ui.item.option : null
});
},
"autocompleteselect input": function( event, ui ) {
ui.item.option.selected = true;
this._trigger( "select", event, {
item: ui.item.option
});
},
"autocompleteopen input": function ( event, ui ) {
this.uiCombo.children('.ui-autocomplete')
.outerWidth(this.uiCombo.outerWidth(true));
},
"mousedown .ui-combobox-button" : function ( event ) {
this._wasOpen = this.uiInput.autocomplete("widget").is(":visible");
},
"click .ui-combobox-button" : function( event ) {
this.uiInput.focus();
// close if already visible
if (this._wasOpen)
return;
// pass empty string as value to search for, displaying all results
this.uiInput.autocomplete("search", "");
}
},
value: function ( newVal ) {
var select = this.element,
valid = false,
selected;
if (!arguments.length) {
selected = select.children(":selected");
return selected.length > 0 ? selected.val() : null;
}
select.prop('selectedIndex', -1);
select.children('option').each(function() {
if ( this.value == newVal ) {
this.selected = valid = true;
return false;
}
});
if ( valid ) {
this.uiInput.val(select.children(':selected').text());
} else {
this.uiInput.val( "" );
this.element.prop('selectedIndex', -1);
}
},
_destroy: function () {
this.element.show();
this.uiCombo.replaceWith( this.element );
},
widget: function () {
return this.uiCombo;
},
_getCreateEventData: function() {
return {
select: this.element,
combo: this.uiCombo,
input: this.uiInput
};
}
});
}(jQuery));
</script>
Change the select event by change event in your binding handler :
function viewModel() {
var self = this;
self.myOptions = ko.observableArray([{
Name: "First option",
Id: 1
},
{
Name: "Second option",
Id: 2
},
{
Name: "Third option",
Id: 3
}
]);
self.selectedOption = ko.observable(3);
}
var myViewModel = new viewModel();
ko.applyBindings(myViewModel);
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.1.0/knockout-min.js"></script>
<html>
<body>
<p> Combobox: </p>
<select id="myCombo" data-bind="options: myOptions, optionsText: 'Name', optionsValue: 'Id', value: selectedOption, valueAllowUnset: true, combo: selectedOption"></select>
<p> Option selected Id: </p>
<texarea data-bind="text: selectedOption()"> </texarea>
</body>
</html>
<script>
// ko-combo.js
(function() {
//combobox
ko.bindingHandlers.combo = {
init: function(element, valueAccessor, allBindingsAccessor) {
//initialize combobox with some optional options
var options = {};
$(element).combobox({
change: function() {
var observable = valueAccessor();
observable($(element).combobox('value'));
}
});
//handle the field changing
ko.utils.registerEventHandler(element, "change", function() {
var observable = valueAccessor();
observable($(element).val());
});
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).combobox("destroy");
});
},
//update the control when the view model changes
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
$(element).combobox('value', value);
}
};
})();
</script>
<script>
// jquery.ui.combobox.js
/*!
* Copyright Ben Olson (https://github.com/bseth99/jquery-ui-extensions)
* jQuery UI ComboBox #VERSION
*
* Adapted from Jörn Zaefferer original implementation at
* http://www.learningjquery.com/2010/06/a-jquery-ui-combobox-under-the-hood
*
* And the demo at
* http://jqueryui.com/autocomplete/#combobox
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
(function( $, undefined ) {
$.widget( "ui.combobox", {
options: {
editable: false
},
version: "#VERSION",
widgetEventPrefix: "combobox",
uiCombo: null,
uiInput: null,
_wasOpen: false,
_create: function() {
var self = this,
select = this.element.hide(),
input, wrapper;
input = this.uiInput =
$( "<input />" )
.insertAfter(select)
.addClass("ui-widget ui-widget-content ui-corner-left ui-combobox-input")
.val( select.children(':selected').text() );
wrapper = this.uiCombo =
input.wrap( '<span>' )
.parent()
.addClass( 'ui-combobox' )
.insertAfter( select );
input
.autocomplete({
delay: 0,
minLength: 0,
appendTo: wrapper,
source: $.proxy( this, "_linkSelectList" )
});
$( "<button>" )
.attr( "tabIndex", -1 )
.attr( "type", "button" )
.insertAfter( input )
.button({
icons: {
primary: "ui-icon-triangle-1-s"
},
text: false
})
.removeClass( "ui-corner-all" )
.addClass( "ui-corner-right ui-button-icon ui-combobox-button" );
// Our items have HTML tags. The default rendering uses text()
// to set the content of the <a> tag. We need html().
input.data( "ui-autocomplete" )._renderItem = function( ul, item ) {
return $( "<li>" )
.append( $( "<a>" ).html( item.label ) )
.appendTo( ul );
};
this._on( this._events );
},
_linkSelectList: function( request, response ) {
var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), 'i' );
response( this.element.children('option').map(function() {
var text = $( this ).text();
if ( this.value && ( !request.term || matcher.test(text) ) ) {
return {
label: text.replace(
new RegExp(
"(?![^&;]+;)(?!<[^<>]*)(" +
$.ui.autocomplete.escapeRegex(request.term) +
")(?![^<>]*>)(?![^&;]+;)", "gi"),
"<strong>$1</strong>"),
value: text,
option: this
};
}
})
);
},
_events: {
"autocompletechange input" : function(event, ui) {
var $el = $(event.currentTarget);
if ( !ui.item ) {
var matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( $el.val() ) + "$", "i" ),
valid = false;
this.element.children( "option" ).each(function() {
if ( $( this ).text().match( matcher ) ) {
this.selected = valid = true;
return false;
}
});
if (!this.options.editable) {
if (!valid) {
// remove invalid value, as it didn't match anything
$el.val("");
this.element.prop('selectedIndex', -1);
//return false;
}
}
}
this._trigger( "change", event, {
item: ui.item ? ui.item.option : null
});
},
"autocompleteselect input": function( event, ui ) {
ui.item.option.selected = true;
this._trigger( "select", event, {
item: ui.item.option
});
},
"autocompleteopen input": function ( event, ui ) {
this.uiCombo.children('.ui-autocomplete')
.outerWidth(this.uiCombo.outerWidth(true));
},
"mousedown .ui-combobox-button" : function ( event ) {
this._wasOpen = this.uiInput.autocomplete("widget").is(":visible");
},
"click .ui-combobox-button" : function( event ) {
this.uiInput.focus();
// close if already visible
if (this._wasOpen)
return;
// pass empty string as value to search for, displaying all results
this.uiInput.autocomplete("search", "");
}
},
value: function ( newVal ) {
var select = this.element,
valid = false,
selected;
if (!arguments.length) {
selected = select.children(":selected");
return selected.length > 0 ? selected.val() : null;
}
select.prop('selectedIndex', -1);
select.children('option').each(function() {
if ( this.value == newVal ) {
this.selected = valid = true;
return false;
}
});
if ( valid ) {
this.uiInput.val(select.children(':selected').text());
} else {
this.uiInput.val( "" );
this.element.prop('selectedIndex', -1);
}
},
_destroy: function () {
this.element.show();
this.uiCombo.replaceWith( this.element );
},
widget: function () {
return this.uiCombo;
},
_getCreateEventData: function() {
return {
select: this.element,
combo: this.uiCombo,
input: this.uiInput
};
}
});
}(jQuery));
</script>

FullCalendar can't figure out correct moment.js format

I'm trying to setup a schedule system in full calendar. With my current code when you click on a day in the calendar it brings up a modal that displays the day and the time you clicked on. This modal then allows you to add events to the calendar based on that day and time.
What I want is to for the app to only pass the day on click and pass the time through the modal.
If i format the date to ("HH:mm:ss YYYY-MM-DD" ) I can pass in the correct date and all is well. The only thing is I do not want the YYYY-MM-DD portion showing up in my modal. If i trim it to just ("HH:mm:ss" ) It updates the calendar but updates that specific time for every day in the calendar indefinitely.
How can i only show the time in the modal box while still passing the year, month and day to the calendar?
My code is
let isPast = ( date ) => {
let today = moment().format();
return moment( today ).isAfter( date );
};
Template.events.onCreated( () => {
let template = Template.instance();
template.subscribe( 'events' );
});
Template.events.onRendered( () => {
$( '#events-calendar' ).fullCalendar({
header: {
center: 'month,agendaWeek'
},
events( start, end, timezone, callback ) {
let data = Events.find().fetch().map( ( event ) => {
event.editable = !isPast( event.start );
return event;
});
if ( data ) {
callback( data );
}
},
eventRender( event, element ) {
element.find( '.fc-content' ).html(
`<h4>${ event.title }</h4>
<p class="guest-count">${ event.guests } Guests</p>
<p class="type-${ event.type }">#${ event.type }</p>
`
);
},
eventDrop( event, delta, revert ) {
let date = event.start.format();
if ( !isPast( date ) ) {
let update = {
_id: event._id,
start: date,
end: date
};
Meteor.call( 'editEvent', update, ( error ) => {
if ( error ) {
Bert.alert( error.reason, 'danger' );
}
});
} else {
revert();
Bert.alert( 'Sorry, you can\'t move items to the past!', 'danger' );
}
},
dayClick( date ) {
Session.set( 'eventModal', { type: 'add', date: date.format("HH:mm:ss YYYY-MM-DD" ) } );
$( '#add-edit-event-modal' ).modal( 'show' );
},
eventClick( event ) {
Session.set( 'eventModal', { type: 'edit', event: event._id } );
$( '#add-edit-event-modal' ).modal( 'show' );
}
});
Tracker.autorun( () => {
Events.find().fetch();
$( '#events-calendar' ).fullCalendar( 'refetchEvents' );
});
});
And
let closeModal = () => {
$( '#add-edit-event-modal' ).modal( 'hide' );
$( '.modal-backdrop' ).fadeOut();
};
Template.addEditEventModal.helpers({
modalType( type ) {
let eventModal = Session.get( 'eventModal' );
if ( eventModal ) {
return eventModal.type === type;
}
},
modalLabel() {
let eventModal = Session.get( 'eventModal' );
if ( eventModal ) {
return {
button: eventModal.type === 'edit' ? 'Edit' : 'Add',
label: eventModal.type === 'edit' ? 'Edit' : 'Add an'
};
}
},
selected( v1, v2 ) {
return v1 === v2;
},
event() {
let eventModal = Session.get( 'eventModal' );
if ( eventModal ) {
return eventModal.type === 'edit' ? Events.findOne( eventModal.event ) : {
start: eventModal.date,
end: eventModal.date
};
}
}
});
Template.addEditEventModal.events({
'submit form' ( event, template ) {
event.preventDefault();
let eventModal = Session.get( 'eventModal' ),
submitType = eventModal.type === 'edit' ? 'editEvent' : 'addEvent',
eventItem = {
title: template.find( '[name="title"]' ).value,
start: template.find( '[name="start"]' ).value,
end: template.find( '[name="end"]' ).value,
type: template.find( '[name="type"] option:selected' ).value,
guests: parseInt( template.find( '[name="guests"]' ).value, 10 )
};
if ( submitType === 'editEvent' ) {
eventItem._id = eventModal.event;
}
Meteor.call( submitType, eventItem, ( error ) => {
if ( error ) {
Bert.alert( error.reason, 'danger' );
} else {
Bert.alert( `Event ${ eventModal.type }ed!`, 'success' );
closeModal();
}
});
},
'click .delete-event' ( event, template ) {
let eventModal = Session.get( 'eventModal' );
if ( confirm( 'Are you sure? This is permanent.' ) ) {
Meteor.call( 'removeEvent', eventModal.event, ( error ) => {
if ( error ) {
Bert.alert( error.reason, 'danger' );
} else {
Bert.alert( 'Event deleted!', 'success' );
closeModal();
}
});
}
}
});
Image of app
I separate out the date and time into two moments like this:
this.dayClick = function(when) {
this.event.selectedTime = when;
this.event.selectedDate = when.clone();
And then use a datepicker for one, and a timepicker for the other. When saving to the database I need to mash them together for a date/time

XDomainRequest sometimes randomly aborts

XDomainRequest most of them time works ok but sometimes aborts in ie9 specially. has anyone experienced this before?
In case you want to see this is the xdr implementation im using:
(function( jQuery ) {
if ( window.XDomainRequest ) {
jQuery.ajaxTransport(function( s ) {
if ( s.crossDomain && s.async ) {
if ( s.timeout ) {
s.xdrTimeout = s.timeout;
delete s.timeout;
}
var xdr;
return {
send: function( _, complete ) {
function callback( status, statusText, responses, responseHeaders ) {
xdr.onload = xdr.onerror = xdr.ontimeout = jQuery.noop;
xdr = undefined;
complete( status, statusText, responses, responseHeaders );
}
xdr = new window.XDomainRequest();
xdr.onload = function() {
callback( 200, "OK", { text: xdr.responseText }, "Content-Type: " + xdr.contentType );
};
xdr.onerror = function() {
callback( 404, "Not Found" );
};
xdr.onprogress = function() {};
if ( s.xdrTimeout ) {
xdr.ontimeout = function() {
callback( 0, "timeout" );
};
xdr.timeout = s.xdrTimeout;
}
xdr.open( s.type, s.url, true );
xdr.send( ( s.hasContent && s.data ) || null );
},
abort: function() {
if ( xdr ) {
xdr.onerror = jQuery.noop();
xdr.abort();
}
}
};
}
});
}
})( jQuery );
I had this issue a while back and I found that if you wrap your send method inside a setTimeout it solves the issue.
setTimeout(function(){
xdr.send( ( s.hasContent && s.data ) || null );
}, 0);

YUI datatable-sort not working

Hello I'm parsing some data from my database into a table using aui-datatable plugin however I would like to add some basic sorting in my columns. Bellow you may see the function "renderProducts" which renders the data-table. Furthermore bellow is a sample column object that is being passed into the table renderer.
var API= (function(Y){
var settings = {
serviceURL : null
};
var getServiceAttribute = function( attribute ) {
return '_openpimadmin_WAR_OpenTest_' + attribute;
}
var getServiceURL = function( service ) {
return settings.serviceURL + '&p_p_resource_id=' + service;
};
var service = function( service, dataSet, handlers ){
var serviceData = {};
var serviceHandlers = {
start : function(){},
success : function(){},
failure : function(){},
end : function(){}
};
for (prop in dataSet) {
serviceData[getServiceAttribute(prop)] = dataSet[prop];
}
for (prop in handlers) {
if ( serviceHandlers.hasOwnProperty(prop) && typeof handlers[prop] === 'function') {
serviceHandlers[prop] = handlers[prop];
}
}
Y.io(
getServiceURL( service ),
{
method : 'POST',
data : serviceData,
on : {
start : function( transactionID ) {
serviceHandlers.start(transactionID);
},
success : function( transactionID, response ) {
var parsed = Y.JSON.parse(response.responseText);
if (parsed.success === true){
serviceHandlers.success( transactionID, parsed );
} else {
console.log('Service [' + service + '] error: ' + parsed.error);
}
},
failure : function( transactionID, response ) {
serviceHandlers.failure( transactionID, response );
},
end : function( transactionID ) {
serviceHandlers.end( transactionID );
}
}
}
);
}
return {
services : {
getProducts : function( dataSet, handlers ){
dataSet = dataSet || {};
handlers = handlers || {};
service( 'getProducts', dataSet, handlers );
},
getProductsTableAttributeHeaders : function( dataSet, handlers) {
dataSet = dataSet || {};
handlers = handlers || {};
service( 'getProductsTableAttributeHeaders', dataSet, handlers );
},
},
views : {
renderProducts : function( el, columns, dataSet ) {
Y.one(el).get('childNodes').remove();
new Y.DataTable.Base({
columnset : columns,
recordset : dataSet,
width: '100%'
}).render(el);
}
},
get : function( prop ) {
return settings[prop];
},
set : function( options ) {
settings = Y.merge( settings, options );
}
};
})( YUI().use('node', 'io', 'aui-datatable', 'datatable-sort', function(Y){return Y;}) );
Column object:
Object {key: "name", label: "Όνομα", allowHTML: true, emptyCellValue: "<em>(not set)</em>", sortable: true}
The issue I'm facing is that sorting is nowhere to be seen, columns dont seem interactive and the user is not able to sort them, although the table is being rendered fine.
Thank you in advance.
PS: I'm new to YUI().
You need to make sure that sortable: true set for column that need sorting.
Here is a real world example with sorting feature
It seems this was the problem:
new Y.DataTable.Base({
columnset : columns,
recordset : dataSet,
width: '100%'
}).render(el);
Should have been like this:
new Y.DataTable({
columnset : columns,
recordset : dataSet,
width: '100%'
}).render(el);

Categories