Expandable textarea field for chat app using angular directive - javascript

I'm using angular/bootstrap and have a particular directive which functions as the actual chat input box to my app. I've gotten the more important expanding behavior done, except I can't quite figure out how to do three things in specific...
Here is the fiddle
1) I need to have the enter key submit the text inside the textarea (using the button of the form is optional, feel free to remove it). I'm getting the following error in my console: "Uncaught ReferenceError: $ is not defined" (lack of knowledge of js structure here).
2) Since I'm using angular, where I should include the custom script? Probably not inline inside tags in the directive...maybe inside controllers.js? What's the best practice for that?
3) The ajax call inside the script this.form.sendThought();, can controllers.js be accessed from the script? I guess #2 above needs to be in a place such that I could access this function...
Here is the directive:
<div class="clear" ng-show="allowInput">
<form role="form">
<div class="input-group">
<div class="textarea-container" id="txtCont">
<textarea ng-model="rawResponse"></textarea>
<div class="textarea-size"></div>
</div>
<span class="input-group-btn">
<button class="btn btn-lg" type="button" ng-click="sendThought()">send</button>
</span>
</div>
</form>
</div>
Script:
var textContainer, textareaSize, input;
var autoSize = function () {
textareaSize.innerHTML = input.value + '\n';
};
document.addEventListener('DOMContentLoaded', function() {
textContainer = document.querySelector('.textarea-container');
textareaSize = textContainer.querySelector('.textarea-size');
input = textContainer.querySelector('textarea');
autoSize();
input.addEventListener('input', autoSize);
});
$('#txtCont').keydown(function() {
if (event.keyCode == 13) {
this.form.sendThought();
return false;
}
});

You don't have jQuery included (which is what the $ global object usually means here). You can either include jQuery or do this with vanilla JS. (Even better, use jqLite from inside the Angular directive!)
Here's what a keyup event looks like with plain JS. You don't have to name the function like I did if you prefer to pass an anonymous function in. I prefer to name them so I can modify later if I need to. (I also think it's cleaner)
document.getElementById('chat').onkeyup = keyPressedChat;
http://jsfiddle.net/k0kyu5t7/
I put that together real quick to help give you an idea about what I mean by using vanilla JS outside the context of your app.
I noticed a couple more problems looking at it a second time (besides not including jQuery):
You should be listening for keydown events on the textarea element instead of the parent div. It's possible to capture keypress events that you might not want unless you point to the text area specifically.
You should be passing the event into your anonymous function callback that you're attaching to the handler.
The answer to your #2 is far too broad for a quick discussion here. There are a lot of opinionated ways on how to organize your files in an Angular app. I personally like to break things down by components and then drop them inside folders like that. Your "chat-directive" could live inside [Root] -> [Chat] -> [Directives] -> chat.js. That's just one way.
As for your #3, it depends on what you want here. You can call services/factories from your directives, which is where your ajax calls should be anyway. If you want your directive to be more modular, then one option might be to pass a method through the directive itself (via the view).

Related

html, js - how to limit elements "scope" - namespaces

During last perioud I've seen more and more often the following situation.
Developer A creates a feature. Let's say is an autocomplete input. Let's assume for simplicity that no framework is used, html and js are on the same file. The file would look like this (let's also asume jquery used - is less to type):
<!-- autocomplete something.html file -->
.........................................
<input id="autocomplete" type="text" />
<script type="text/javascript">
$('#autocomplete').change(function() {
// Do lots of things here -> ajax request, parsing, etc.
});
</script>
.........................................
Now developer B sees the feature and says "Oh, nice feature, I can use that in my section, I'll put one at the top, and one at the bottom - because page is long".
And he does an ajax call to get that html file (this is important, I'm talking about features loaded like this, not features rewritten for the other section) and includes it where he needs it.
Now... problem. The first autocomple works, the second doesn't (because is select by id).
A workaround would be to modify, and use a class. And everything is ok, unless someone else (or himself, or whatever) uses same class for a tottaly different thing.
This could all be avoided if you could the the script to use as a "scope" (I know it is not the correct phrasing, couldn't find any better) the file where it was declared on.
Note: This is a theoretical question. For each particular case a solution could be found, but defining some kind of namespaces for this scenarios would solve the whole class of problems.
How could that be achieved?
As long as you're accessing the DOM the only way I see would be to use the "old" inline event:
<!-- autocomplete something.html file -->
.........................................
<input onchange="autocomplete_change();" type="text" />
<script type="text/javascript">
function autocomplete_change() {
// Do lots of things here -> ajax request, parsing, etc.
};
</script>
...........
Now the function name cannot be used by Developer B.
But when accessing DOM some kind of restriction will always be there.
However when creating the input by Script you can bind the handler directly to the Element:
<script>
var input = $('<input type="text"/>');
input.change(function() {
// Do lots of things here -> ajax request, parsing, etc.
});
document.body.appendChild(input[0]);
</script>

The effective way to call javascript initializing form?

One often have form with some dynamic parts, that needs to be initialized onload. E.g. datepickers, enhanced selects, section toggling, hiding/showing conditional elements etc.
Example:
<form>
<input type="text" name="date">
<select name="selection"></select>
</form>
and I want to init datepicker on the date element and Select2 on the selection element.
Where to put the form initialization?
My thoughts:
Init throught global selector:
$(function() {
$('input[name=date]').datepicker();
$('select[name=selection]').select2();
})`.
But I have one js file for the whole web, so this would led to crawling the whole DOM on each page load, even if the element is not present on current page.
Some kind of conditional selector. E.g. give <body> and id and add to my global js file something like this: $(function() { $('input[name=date]', 'body#foo').datepicker(); })
Encapsulate the init for each form into a function (or class method), and call the function from HTML:
<script type="text/javascript">
$(document).ready(initMyForm());
</script>
But I'm guessing, isn't there any better way? What would you suggest, especially for bigger projects with many different forms requiring different javascript initialization?
If for your current project you are running one JS file, or even for medium-size projects where a 'generic' form setup function is appropriate, using a function as you described would be appropriate.
See example below, with the function wrapped as a small jQuery plugin, so you can call this on specific selectors as required, to avoid running through the whole DOM.
;(function($){
$.extend({
initMyForm : function(){
$(this).find('input[name=date]').datepicker();
$(this).find('select[name=selection]').select2();
}
});
})(jQuery);
So you can use this like:
$(document).ready(function(){
$(body).find(form).initMyForm();
});
$("#my-form").initMyForm();
$(".page-content .form").initMyForm();

Inline Editing But Instance Doesn't Exist

I have my own custom non-jQuery ajax which I use for programming web applications. I recently ran into problems with IE9 using TinyMCE, so am trying to switch to CKeditor
The editable text is being wrapped in a div, like so:
<div id='content'>
<div id='editable' contenteditable='true'>
page of inline text filled with ajax when links throughout the site are clicked
</div>
</div>
When I try to getData on the editable content using the examples in the documentation, I get an error.
I do this:
CKEDITOR.instances.editable.getData();
And get this:
Uncaught TypeError: Cannot call method 'getData' of undefined
So I figure that it doesn't know where the editor is in the dom... I've tried working through all editors to get the editor name, but that doesn't work-- no name appears to be found.
I've tried this:
for(var i in CKEDITOR.instances) {
alert(CKEDITOR.instances[i].name);
}
The alert is just blank-- so there's no name associated with it apparently.
I should also mention, that despite my best efforts, I cannot seem to get the editable text to have a menu appear above it like it does in the Massive Inline Editing Example
Thanks for any assistance you can bring.
Jason Silver
UPDATE:
I'm showing off my lack of knowledge here, but I had never come across "contenteditable='true'" before, so thought that because I was able to type inline, therefore the editor was instantiated somehow... but now I'm wondering if the editor is even being applied to my div.
UPDATE 2:
When the page is loaded and the script is initially called, the div does not exist. The editable div is sent into the DOM using AJAX. #Zee left a comment below that made me wonder if there is some other command that should be called in order to apply the editor to that div, so I created a button in the page with the following onclick as a way to test this approach: (adapted from the ajax example)
var editor,html='';config = {};editor=CKEDITOR.appendTo('editable',config, html );
That gives the following error in Chrome:
> Uncaught TypeError: Cannot call method 'equals' of undefined
> + CKEDITOR.tools.extend.getEditor ckeditor.js:101
> b ckeditor.js:252
> CKEDITOR.appendTo ckeditor.js:257
> onclick www.pediatricjunction.com:410
Am I headed in the right direction? Is there another way to programmatically tell CKEditor to apply the editor to a div?
UPDATE 3:
Thanks to #Reinmar I had something new to try. The most obvious way for me to test to see if this was the solution was to put a button above the content editable div that called CKEDITOR.inlineAll() and inline('editable') respectively:
<input type='button' onclick=\"CKEDITOR.inlineAll();\" value='InlineAll'/>
<input type='button' onclick=\"CKEDITOR.inline('editable');\" value='Inline'/>
<input type='button' onclick=\"var editor = CKEDITOR.inline( document.getElementById( 'editable' ) );\" value='getElementById'/>
This returned the same type of error in Chrome for all three buttons, namely:
Uncaught TypeError: Cannot call method 'equals' of undefined ckeditor.js:101
+ CKEDITOR.tools.extend.getEditor ckeditor.js:101
CKEDITOR.inline ckeditor.js:249
CKEDITOR.inlineAll ckeditor.js:250
onclick
UPDATE 4:
Upon further fiddling, I've tracked down the problem being related to json2007.js, which is a script I use which works with Real Simple History (RSH.js). These scripts have the purpose of tracking ajax history, so as I move forward and back through the browser, the AJAX page views is not lost.
Here's the fiddle page: http://jsfiddle.net/jasonsilver/3CqPv/2/
When you want to initialize inline editor there are two ways:
If element which is editable (has contenteditable attribute) exists when page is loaded CKEditor will automatically initialize an instance for it. Its name will be taken from that element's id or it will be editor<number>. You can find editors initialized automatically on this sample.
If this element is created dynamically, then you need to initialize editor on your own.
E.g. after appending <div id="editor" contenteditable="true">X</div> to the document you should call:
CKEDITOR.inline( 'editor' )
or
CKEDITOR.inlineAll()
See docs and docs.
You can find editor initialized this way on this sample.
The appendTo method has different use. You can initialize themed (not inline) editor inside specified element. This method also accepts data of editor (as 3rd arg), when all other methods (CKEDITOR.inline, CKEDITOR.replace, CKEDITOR.inlineAll) take data from the element they are replacing/using.
Update
I checked that libraries you use together with CKEditor are poorly written and cause errors you mentioned. Remove json2007.js and rsh.js and CKEditor works fine.
OK, so I have tracked down the problem.
The library I was using for tracking Ajax history and remembering commands for the back button, called Real Simple History, was using a script called json2007 which was intrusive and extended native prototypes to the point where things broke.
RSH.js is kind of old, and I wasn't using it to it's full potential anyway, so my final solution was to rewrite the essential code I needed for that, namely, a listener that watched for anchor (hash) changes in the URL, then parsed those changes and resubmitted the ajax command.
var current_hash = window.location.hash;
function check_hash() {
if ( window.location.hash != current_hash ) {
current_hash = window.location.hash;
refreshAjax();
}
}
hashCheck = setInterval( "check_hash()", 50 );
'refreshAjax()' was an existing function anyway, so this is actually a more elegant solution than I was using with Real Simple History.
After stripping out the json2007.js script, everything else just worked, and CKEditor is beautiful.
Thanks so much for your help, #Reinmar... I appreciate your patience and effort.

User control with a client side API

Maybe I've picked a totally inappropriate/bad example.
What I have is a user control that contains a bunch of dynamically created Telerik RadGrids.
My user control is added to a couple of Telerik RadPageViews that are part of a RadMultiPage that is used alongside a RadTabStrip.
What I need to do is call a Javascript function in my usercontrol to update it's display whenever it's parent RadPageView is selected.
So basically I have the following Javascript code:
function OnClientTabSelected(sender, args)
{
// Get the MyControl that is on this tab
var myControl = $find("whatever");
// Call a method that updates the display
myControl.doSomething();
}
Thanks,
David
You can add a wrapper div in your User Control and then extend that div using jQuery to add your desired methods and properties. The trick is to set the div's id='<%=this.ID%>' - that way the div has the same ID as the User Control (which is fine because the User Control doesn't actually render anything - only its contents).
Then back on your containing page, you can just reference your UserControl's ID using $get('whatever') - and you'll actually select your extended div.. which will have all your methods and properties on it.
The nice thing about this approach is that all of your methods and properties and neatly scoped and nothing is in the global namespace.
I have a full demo solution and details here if you want more info:
http://programmerramblings.blogspot.com/2011/07/clientside-api-for-aspnet-user-controls.html
Just make a call to javascript method in input button if you are sure about the name of that function.
<input type="button" value="test" onclick="doSomething()" />
If you place any javascript code in the control that will be spit on the page and it will be available for calling provided both of them are in the same form.
for example your code will look like this if you look into the source of that page.
<script type="text/javascript">
function doSomething()
{
alert(new Date());
}
</script>
<div>
<span id="MyControl1_Label1">Dummy label</span>
</div>
<hr />
<input type="button" value="test" onclick="doSomething()" />
Edit: This is not a good way to access these methods in my opinion. When you are putting some javascript code inside a control then it should be used in that control only (There is no rule as such, its just a design suggestion). If you are trying to access javascript code of a control from outside that control then you need to revisit your design.
If you can give us more details on why you want to access that method, may be we can suggest some better way to do that.
Update: (As you have modified your question): Bit tricky to answer this as I dont have hands on experience with Rad controls. I guess there should be some feature which will help you to update the controls in that page without using javascript, may be have a look at clientside-API provided for Rad.
May be somebody who knows about RAD controls will help you.
You should make the control method public
public void doSomething
and call this from the page
myControl1.doSomething();

web2py: How do I add javascript to Web2py Crud form?

Working with Web2Py. I'm trying to attach some javascript either to a field (onchange) or to the form (onsubmit), but I see absolutely no way to pass such argument to crud.create or to form.custom.widget.
Anyone has an idea?
Of course there is a way. The appropriate way is to ask people on the web2py mailing list who know how to, as opposed to generic stack overflow users who will guess an incorrect answer. :-)
Anyway, assume you have:
db.define_table('image',
Field('name'),
Field('file', 'upload'))
You can do
def upload_image():
form=crud.create(db.image)
form.element(name='file')['_onchange']='... your js here ...'
form.element('form')['_onsubmit']='... your js here ...'
return dict(form=form)
Element takes the css3/jQuery syntax (but it is evaluated in python).
I do not believe there is a way to do this directly. One option is just to manipulate web2py generated HTML, it is just a string. Even cleaner, in my opinion, is just to bind the event using jQuery's $(document).ready() function.
Say you have a database table (all is stolen from web2py's docs):
db.define_table('image',
Field('name'),
Field('file', 'upload'))
With form:
def upload_image():
return dict(form=crud.create(db.image))
Embedded in a view (in the simplest manner):
{{=form}}
And you want to add an onblur handler to the name input field (added to the view):
<script type="text/javascript">
$(document).ready(function(){
$("#image_name").blur(function(){
// do something with image name loses focus...
});
});
</script>

Categories

Resources