Esempio n. 1
0
        internal override void SubscribeToClickEventForChildContainerDiv(dynamic divWhereToPlaceChild, dynamic checkBoxDomElement)
        {
            if (INTERNAL_CheckBoxAndRadioButtonHelpers.IsRunningInJavaScript())
            {
#if !BRIDGE
                JSIL.Verbatim.Expression(@"
$0.addEventListener('click', function(e) {
    if($1.checked === true)
    {
        $1.checked = false;
    }
    else
    {
        $1.checked = true;
    }
    var evt = document.createEvent('Event');
    evt.initEvent('change', false, false);
    $1.dispatchEvent(evt);
}, false);", divWhereToPlaceChild, checkBoxDomElement);
#else
                Script.Write(@"
{0}.addEventListener('click', function(e) {
    if({1}.checked === true)
    {
        {1}.checked = false;
    }
    else
    {
        {1}.checked = true;
    }
    var evt = document.createEvent('Event');
    evt.initEvent('change', false, false);
    {1}.dispatchEvent(evt);
}, false);", divWhereToPlaceChild, checkBoxDomElement);
#endif
            }
#if !BRIDGE
            else
            {
                // ---- SIMULATOR ----
                string javaScriptToExecute = string.Format(@"
var divWhereToPlaceChild = document.getElementById(""{0}"");
divWhereToPlaceChild.addEventListener('click', function(e) {{
var checkBoxDomElement = document.getElementById(""{1}"");
    if(checkBoxDomElement.checked === true)
    {{
        checkBoxDomElement.checked = false;
    }}
    else
    {{
        checkBoxDomElement.checked = true;
    }}
    var evt = document.createEvent('Event');
    evt.initEvent('change', false, false);
    checkBoxDomElement.dispatchEvent(evt);
}}, false);", ((INTERNAL_HtmlDomElementReference)divWhereToPlaceChild).UniqueIdentifier, ((INTERNAL_HtmlDomElementReference)checkBoxDomElement).UniqueIdentifier);
                INTERNAL_HtmlDomManager.ExecuteJavaScript(javaScriptToExecute);
            }
#endif
        }
Esempio n. 2
0
        internal void OnMaxLengthChanged(int maxLength)
        {
            if (INTERNAL_VisualTreeManager.IsElementInVisualTree(this) &&
                INTERNAL_HtmlDomManager.IsNotUndefinedOrNull(_contentEditableDiv))
            {
                if (!IsRunningInJavaScript())
                {
                    //--- SIMULATOR ONLY: ---
                    // Set the "data-maxlength" property (that we have made up) so that the "keydown" JavaScript event can retrieve this value:
                    INTERNAL_HtmlDomManager.ExecuteJavaScript($@"
var element = document.getElementByIdSafe(""{((INTERNAL_HtmlDomElementReference)_contentEditableDiv).UniqueIdentifier}"");
element.setAttribute(""data-maxlength"", ""{maxLength}"");");
                }
            }
        }
Esempio n. 3
0
        internal void OnAcceptsReturnChanged(bool acceptsReturn)
        {
            if (INTERNAL_VisualTreeManager.IsElementInVisualTree(this) &&
                INTERNAL_HtmlDomManager.IsNotUndefinedOrNull(_contentEditableDiv))
            {
                if (!IsRunningInJavaScript())
                {
                    //--- SIMULATOR ONLY: ---
                    // Set the "data-accepts-return" property (that we have invented) so that the "keydown" JavaScript event can retrieve this value:
                    INTERNAL_HtmlDomManager.ExecuteJavaScript($@"
var element = document.getElementByIdSafe(""{((INTERNAL_HtmlDomElementReference)_contentEditableDiv).UniqueIdentifier}"");
element.setAttribute(""data-acceptsreturn"", ""{acceptsReturn.ToString().ToLower()}"");");
                }
            }
        }
        public static void QueueJavaScriptCode(string javaScriptCode)
        {
#if !DISABLE_SIMULATOR_PERFORMANCE_OPTIMIZATION
            // If we are in a transaction, we queue the code, otherwise we execute it straight away.
            if (IsRunningInsideATransaction)
            {
                _pendingJavaScript.Add(javaScriptCode);
            }
            else
            {
                INTERNAL_HtmlDomManager.ExecuteJavaScript(javaScriptCode);
            }
#else
            INTERNAL_HtmlDomManager.ExecuteJavaScript(javaScriptCode);
#endif
        }
        // This will execute all the pending JavaScript, and reset all nested transactions:
        public static void Flush()
        {
            // Reset the counter of nested transactions:
            _nestedTransactionsCounter = 0;

            // Execute all pending JavaScript:
            if (_pendingJavaScript.Count > 0)
            {
                _pendingJavaScript.Add(@"//------ Transaction Flush ------");
                string javaScriptCodeToExecute = string.Join("\r\n", _pendingJavaScript);
                INTERNAL_HtmlDomManager.ExecuteJavaScript(javaScriptCodeToExecute);
            }

            // Clear the list of pending JavaScript:
            _pendingJavaScript.Clear();
        }
Esempio n. 6
0
        internal void OnIsReadOnlyChanged(bool isReadOnly)
        {
            if (INTERNAL_VisualTreeManager.IsElementInVisualTree(this) &&
                INTERNAL_HtmlDomManager.IsNotUndefinedOrNull(_contentEditableDiv))
            {
                OpenSilver.Interop.ExecuteJavaScriptAsync(
                    "$0.setAttribute(\"contentEditable\", $1);",
                    _contentEditableDiv, (!isReadOnly).ToString().ToLower()
                    );

                if (!IsRunningInJavaScript())
                {
                    //--- SIMULATOR ONLY: ---
                    INTERNAL_HtmlDomManager.ExecuteJavaScript($@"
var element = document.getElementByIdSafe(""{((INTERNAL_HtmlDomElementReference)_contentEditableDiv).UniqueIdentifier}"");
element.setAttribute(""data-isreadonly"",""{isReadOnly.ToString().ToLower()}"");");
                }
            }
        }
Esempio n. 7
0
        void ExecuteJS_SimulatorOnly(string javascript, object canvasDomElement)
        {
            string str = string.Format(@"
{0}
var cvas = document.getElementById(""{1}"");
if(cvas != undefined && cvas != null) {{
var context = cvas.getContext('2d');
context.save();
context.rotate(angle);
context.translate(offset, 0);
var pat = context.createPattern(canvas, 'repeat');

context.fillStyle = pat;
context.fill();
context.restore();
}}", javascript, ((INTERNAL_HtmlDomElementReference)canvasDomElement).UniqueIdentifier);

            INTERNAL_HtmlDomManager.ExecuteJavaScript(str);
        }
        internal static object ExecuteJavaScript_SimulatorImplementation(string javascript, bool runAsynchronously, bool noImpactOnPendingJSCode = false, params object[] variables)
        {
#if !BUILDINGDOCUMENTATION
            //---------------
            // Due to the fact that it is not possible to pass JavaScript objects between the simulator JavaScript context
            // and the C# context, we store the JavaScript objects in a global dictionary inside the JavaScript context.
            // This dictionary is named "jsSimulatorObjectReferences". It associates a unique integer ID to each JavaScript
            // object. In C# we only manipulate those IDs by manipulating instances of the "JSObjectReference" class.
            // When we need to re-use those JavaScript objects, the C# code passes to the JavaScript context the ID
            // of the object, so that the JavaScript code can retrieve the JavaScript object instance by using the
            // aforementioned dictionary.
            //---------------

            // Verify the arguments:
            if (noImpactOnPendingJSCode && runAsynchronously)
            {
                throw new ArgumentException("You cannot set both 'noImpactOnPendingJSCode' and 'runAsynchronously' to True. The 'noImpactOnPendingJSCode' only has meaning when running synchronously.");
            }

            // Make sure the JS to C# interop is set up:
            if (!IsJavaScriptCSharpInteropSetUp)
            {
#if CSHTML5BLAZOR
                if (Interop.IsRunningInTheSimulator_WorkAround)
                {
#endif
                // Adding a property to the JavaScript "window" object:
                JSObject jsWindow = (JSObject)INTERNAL_HtmlDomManager.ExecuteJavaScriptWithResult("window");
                jsWindow.SetProperty("onCallBack", new OnCallBack(CallbacksDictionary));
#if CSHTML5BLAZOR
            }
            else
            {
                OnCallBack.SetCallbacksDictionary(CallbacksDictionary);
            }
#endif
                IsJavaScriptCSharpInteropSetUp = true;
            }

            string unmodifiedJavascript = javascript;

            // If the javascript code has references to previously obtained JavaScript objects, we replace those references with calls to the "document.jsSimulatorObjectReferences" dictionary.
            for (int i = variables.Length - 1; i >= 0; i--) // Note: we iterate in reverse order because, when we replace ""$" + i.ToString()", we need to replace "$10" before replacing "$1", otherwise it thinks that "$10" is "$1" followed by the number "0". To reproduce the issue, call "ExecuteJavaScript" passing 10 arguments and using "$10".
            {
                var variable = variables[i];
                if (variable is INTERNAL_JSObjectReference)
                {
                    //----------------------
                    // JS Object References
                    //----------------------

                    var    jsObjectReference = (INTERNAL_JSObjectReference)variable;
                    string jsCodeForAccessingTheObject;

                    if (jsObjectReference.IsArray)
                    {
                        jsCodeForAccessingTheObject = string.Format(@"document.jsSimulatorObjectReferences[""{0}""][{1}]", jsObjectReference.ReferenceId, jsObjectReference.ArrayIndex);
                    }
                    else
                    {
                        jsCodeForAccessingTheObject = string.Format(@"document.jsSimulatorObjectReferences[""{0}""]", jsObjectReference.ReferenceId);
                    }

                    javascript = javascript.Replace("$" + i.ToString(), jsCodeForAccessingTheObject);
                }
                else if (variable is INTERNAL_HtmlDomElementReference)
                {
                    //------------------------
                    // DOM Element References
                    //------------------------

                    string id = ((INTERNAL_HtmlDomElementReference)variable).UniqueIdentifier;
                    javascript = javascript.Replace("$" + i.ToString(), string.Format(@"document.getElementById(""{0}"")", id));
                }
                else if (variable is INTERNAL_SimulatorJSExpression)
                {
                    //------------------------
                    // JS Expression (simulator only)
                    //------------------------

                    string expression = ((INTERNAL_SimulatorJSExpression)variable).Expression;
                    javascript = javascript.Replace("$" + i.ToString(), expression);
                }
                else if (variable is Delegate)
                {
                    //-----------
                    // Delegates
                    //-----------

                    Delegate callback = (Delegate)variable;

                    // Add the callback to the document:
                    int callbackId = ReferenceIDGenerator.GenerateId();
                    CallbacksDictionary.Add(callbackId, callback);

#if CSHTML5NETSTANDARD
                    //Console.WriteLine("Added ID: " + callbackId.ToString());
#endif

                    // Change the JS code to point to that callback:
                    javascript = javascript.Replace("$" + i.ToString(), string.Format(
                                                        @"(function() {{
                        var argsArray = Array.prototype.slice.call(arguments);
                        var idWhereCallbackArgsAreStored = ""callback_args_"" + Math.floor(Math.random() * 1000000);
                        document.jsSimulatorObjectReferences[idWhereCallbackArgsAreStored] = argsArray;
                        setTimeout(
                            function() 
                            {{
                               window.onCallBack.OnCallbackFromJavaScript({0}, idWhereCallbackArgsAreStored, argsArray);
                            }}
                            , 1);
                      }})", callbackId));

                    // Note: generating the random number in JS rather than C# is important in order to be able to put this code inside a JavaScript "for" statement (cf. deserialization code of the JsonConvert extension, and also ZenDesk ticket #974) so that the "closure" system of JavaScript ensures that the number is the same before and inside the "setTimeout" call, but different for each iteration of the "for" statement in which this piece of code is put.
                    // Note: we store the arguments in the jsSimulatorObjectReferences that is inside the JS context, so that the user can access them from the callback.
                    // Note: "Array.prototype.slice.call" will convert the arguments keyword into an array (cf. http://stackoverflow.com/questions/960866/how-can-i-convert-the-arguments-object-to-an-array-in-javascript )
                    // Note: in the command above, we use "setTimeout" to avoid thread/locks problems.
                }
                else if (variable == null)
                {
                    //--------------------
                    // Null
                    //--------------------

                    javascript = javascript.Replace("$" + i.ToString(), "null");
                }
                else
                {
                    //--------------------
                    // Simple value types or other objects (note: this includes objects that override the "ToString" method, such as the class "Uri")
                    //--------------------

                    javascript = javascript.Replace("$" + i.ToString(), INTERNAL_HtmlDomManager.ConvertToStringToUseInJavaScriptCode(variable));
                }
            }

            UnmodifiedJavascriptCalls.Add(unmodifiedJavascript);
            // Add the callback to the document:
            if (!CallbacksDictionary.ContainsKey(0))
            {
                CallbacksDictionary.Add(0, (Action <string, int>)ShowErrorMessage);
            }

#if CSHTML5NETSTANDARD
            //Console.WriteLine("Added ID: " + callbackId.ToString());
#endif

            // Change the JS code to call ShowErrorMessage in case of error:
            string errorCallBack = string.Format(
                @"var idWhereErrorCallbackArgsAreStored = ""callback_args_"" + Math.floor(Math.random() * 1000000);
                document.jsSimulatorObjectReferences[idWhereErrorCallbackArgsAreStored] = {0};
                var argsArr = [];
                argsArr[0] = error.message;
                argsArr[1] = {0};
window.onCallBack.OnCallbackFromJavaScript(0, idWhereErrorCallbackArgsAreStored, argsArr);", IndexOfNextUnmodifiedJSCallInList
                );
            ++IndexOfNextUnmodifiedJSCallInList;

            // Surround the javascript code with some code that will store the result into the "document.jsSimulatorObjectReferences" for later use in subsequent calls to this method:
            int referenceId = ReferenceIDGenerator.GenerateId();
            javascript = string.Format(
                @"
try {{
var result = eval(""{0}"");
document.jsSimulatorObjectReferences[""{1}""] = result;
result;
}}
catch (error) {{
    eval(""{2}"");
}}
result;
", INTERNAL_HtmlDomManager.EscapeStringForUseInJavaScript(javascript), referenceId, INTERNAL_HtmlDomManager.EscapeStringForUseInJavaScript(errorCallBack));

            // Execute the javascript code:
            object value = null;
            if (!runAsynchronously)
            {
                value = CastFromJsValue(INTERNAL_HtmlDomManager.ExecuteJavaScriptWithResult(javascript, noImpactOnPendingJSCode: noImpactOnPendingJSCode));
            }
            else
            {
                INTERNAL_HtmlDomManager.ExecuteJavaScript(javascript);
            }

            var objectReference = new INTERNAL_JSObjectReference()
            {
                Value       = value,
                ReferenceId = referenceId.ToString()
            };

            return(objectReference);
#else
            return(null);
#endif
        }
Esempio n. 9
0
        private object AddContentEditableDomElement(object parentRef, out object domElementWhereToPlaceChildren)
        {
            bool   isReadOnly = this.Host.IsReadOnly;
            object outerDiv;
            var    outerDivStyle   = INTERNAL_HtmlDomManager.CreateDomElementAppendItAndGetStyle("div", parentRef, this, out outerDiv);
            string backgroundColor = "transparent"; //value when it is templated

            outerDivStyle.backgroundColor = backgroundColor;
            outerDivStyle.height          = "100%";

            object middleDiv;
            var    middleDivStyle = INTERNAL_HtmlDomManager.CreateDomElementAppendItAndGetStyle("div", outerDiv, this, out middleDiv);

            middleDivStyle.width  = "100%";
            middleDivStyle.height = "100%";

            object contentEditableDiv;
            var    contentEditableDivStyle = INTERNAL_HtmlDomManager.CreateDomElementAppendItAndGetStyle("div", middleDiv, this, out contentEditableDiv);

            _contentEditableDiv = contentEditableDiv;
            Host.UpdateFocusContentEditable(_contentEditableDiv);
            if (INTERNAL_HtmlDomManager.IsInternetExplorer())
            {
                //set the class to remove margins on <p> inside the contentEditableDiv
                OpenSilver.Interop.ExecuteJavaScript(@"$0.classList.add(""ie_set_p_margins_to_zero"")", contentEditableDiv);
            }

            contentEditableDivStyle.width  = "100%";
            contentEditableDivStyle.height = "100%";

            // Apply Host.TextWrapping
            contentEditableDivStyle.whiteSpace = Host.TextWrapping == TextWrapping.NoWrap ? "nowrap" : "pre-wrap";

            // Apply Host.HorizontalScrollBarVisibility
            contentEditableDivStyle.overflowX = ScrollBarVisibilityToHtmlString(Host.HorizontalScrollBarVisibility) ?? "hidden";

            // Apply Host.VerticalScrollBarVisibility
            contentEditableDivStyle.overflowY = ScrollBarVisibilityToHtmlString(Host.VerticalScrollBarVisibility) ?? "hidden";

            contentEditableDivStyle.outline    = "solid transparent"; // Note: this is to avoind having the weird border when it has the focus. I could have used outlineWidth = "0px" but or some reason, this causes the caret to not work when there is no text.
            contentEditableDivStyle.background = "solid transparent";
            contentEditableDivStyle.cursor     = "text";

            // Apply TextAlignment
            UpdateTextAlignment(contentEditableDivStyle, Host.TextAlignment);

            string isContentEditable = (!isReadOnly).ToString().ToLower();

            INTERNAL_HtmlDomManager.SetDomElementAttribute(contentEditableDiv, "contentEditable", isContentEditable);
            this.INTERNAL_OptionalSpecifyDomElementConcernedByFocus = contentEditableDiv;
            this.INTERNAL_OptionalSpecifyDomElementConcernedByMinMaxHeightAndWidth = contentEditableDiv;

            contentEditableDivStyle.minWidth  = "14px";
            contentEditableDivStyle.minHeight = (Math.Floor(this.Host.FontSize * 1.5 * 1000) / 1000).ToInvariantString() + "px"; // Note: We multiply by 1000 and then divide by 1000 so as to only keep 3 decimals at the most. //Note: setting "minHeight" is for FireFox only, because other browsers don't seem to need it. The "1.5" factor is here to ensure that the resulting Height is the same as that of the PasswordBox.

            // Fix for Internet Explorer: when pressing Enter in the ContentEditable div, IE will create a new paragraph, which results in graphical issues to the distance between paragraphs. To fix this issue, we put an empty DIV inside by default. When IE detects that there are DIVs inside, it adds a new DIV instead of creating a new paragraph when the user presses Enter.
            if (INTERNAL_HtmlDomManager.IsInternetExplorer())
            {
                object divToImproveIELineBreaks;
                var    divToImproveIELineBreaksStyle = INTERNAL_HtmlDomManager.CreateDomElementAppendItAndGetStyle("div", contentEditableDiv, this, out divToImproveIELineBreaks);
            }

            domElementWhereToPlaceChildren = contentEditableDiv;

            // Set the mark saying that the pointer events must be "absorbed" by the TextBox:
            INTERNAL_HtmlDomManager.SetDomElementProperty(outerDiv, "data-absorb-events", true);

            //-----------------------
            // Prepare to raise the "TextChanged" event and to update the value of the "Text" property when the DOM text changes:
            //-----------------------
            //todo: why did we put this here instead of in INTERNAL_AttachToDomEvents?
            if (IsRunningOnInternetExplorer())
            {
                //-----------------------
                // Fix "input" event not working under IE:
                //-----------------------
                this.GotFocus  += InternetExplorer_GotFocus;
                this.LostFocus += InternetExplorer_LostFocus;
                INTERNAL_EventsHelper.AttachToDomEvents("textinput", contentEditableDiv, (Action <object>)(e =>
                {
                    InternetExplorer_RaiseTextChangedIfNecessary();
                }));
                INTERNAL_EventsHelper.AttachToDomEvents("paste", contentEditableDiv, (Action <object>)(e =>
                {
                    InternetExplorer_RaiseTextChangedIfNecessary();
                }));
                INTERNAL_EventsHelper.AttachToDomEvents("cut", contentEditableDiv, (Action <object>)(e =>
                {
                    InternetExplorer_RaiseTextChangedIfNecessary();
                }));
                INTERNAL_EventsHelper.AttachToDomEvents("keyup", contentEditableDiv, (Action <object>)(e =>
                {
                    InternetExplorer_RaiseTextChangedIfNecessary();
                }));
                INTERNAL_EventsHelper.AttachToDomEvents("delete", contentEditableDiv, (Action <object>)(e =>
                {
                    InternetExplorer_RaiseTextChangedIfNecessary();
                }));
                INTERNAL_EventsHelper.AttachToDomEvents("mouseup", contentEditableDiv, (Action <object>)(e =>
                {
                    InternetExplorer_RaiseTextChangedIfNecessary();
                }));
            }
            else
            {
                //-----------------------
                // Modern browsers
                //-----------------------
                INTERNAL_EventsHelper.AttachToDomEvents("input", contentEditableDiv, (Action <object>)(e =>
                {
                    TextAreaValueChanged();
                }));
            }

            //-----------------------
            // Prevent pressing Enter for line break when "AcceptsReturn" is false and prevent pressing any other key than backspace when "MaxLength" is reached:
            // Also prevent the default event when pressing tab and add "\t" when "AcceptsTab" is set to true:
            //-----------------------
            if (IsRunningInJavaScript())
            {
#if OPENSILVER
                throw new InvalidOperationException();
#elif BRIDGE
                //BRIDGETODO : here the code below works weird
                // instance = this
                // makes instance working properly, where it shouldn't
                //Note: I think the reason why instance = this works is because it is set with the correct context of "this",
                //      so instance = this TextBox. If it was inside the function defined as callback of addEventListener,
                //      "this" would be in the context of the event triggered, so it would be the contentEditable div.
                Bridge.Script.Write(@"
var instance = $1;
$0.addEventListener('keydown', function(e) {
    if (e.keyCode == 13 && instance.AcceptsReturn !== true)
    {
        e.preventDefault();
        return false;
    }

    var isAddingTabulation = e.keyCode == 9 && instance.AcceptsTab == true;
    var isRemovingTabulation = isAddingTabulation && e.shiftKey;
    if(isRemovingTabulation)
    {
        isAddingTabulation = false
    }

    if((isAddingTabulation || e.keyCode == 13 || e.keyCode == 32 || e.keyCode > 47) && instance.MaxLength != 0)
    {
        var textLength = instance.GetTextLengthIncludingNewLineCompensation();

        if (e.keyCode == 13)
        {
            textLength += 1; //because adding a new line takes 2 characters instead of 1.
        }
        if(textLength >= instance.MaxLength)
        {
            e.preventDefault();
            return false;
        }
    }

    if(isAddingTabulation)
    {
        //we need to add '\t' where the cursor is, prevent the event (which would change the focus) and dispatch the event for the text changed:
        var sel, range;
        if (window.getSelection) {
            sel = window.getSelection();
            if (sel.rangeCount) {
                range = sel.getRangeAt(0);
                range.deleteContents();
                range.insertNode(document.createTextNode('\t'));
                sel.collapseToEnd();
                range.collapse(false); //for IE
            }
        } else if (document.selection && document.selection.createRange) {
            range = document.selection.createRange();
            range.text = '\t';
            document.selection.collapseToEnd();
        }
        if (window.IS_EDGE)
        {
            sel.removeAllRanges();
            sel.addRange(range);
        }

        instance.TextAreaValueChanged(); //todo: test this.
        e.preventDefault();
            return false;
    }
    if (isRemovingTabulation)
    {
        var sel, range;
        if (window.getSelection) {
            sel = window.getSelection();
            if (sel.rangeCount) {
                range = sel.getRangeAt(0);
                if(range.collapsed)
                {
                    //if the previous character is a '\t', we want to remove it:
                    var rangeStartContainer = range.startContainer;
                    var rangeStartOffset = range.startOffset;

                    //we get the node that contains the text that needs changing and the index of the character to remove:
                    var textNodeToModify = undefined;
                    var indexToRemove = -1;
                    if(rangeStartContainer.nodeType == Node.TEXT_NODE && rangeStartOffset > 0)
                    {
        
                        textNodeToModify = rangeStartContainer;
                        indexToRemove = rangeStartOffset - 1;
                    }
                    else //The potential tab to remove is in the node that is right before the selection(caret) so we look at the last character of the 'previous sibling':
                    {
                        var previousSibling = undefined;

                        // we get the previous node:
                        if(rangeStartContainer.nodeType == Node.TEXT_NODE) //the caret was right before the first element of a textNode. I am not sure if we can arrive here since this case seems to be considered as 'the caret is between the nodes' instead of 'the caret is in the node but before the first character'
                        {
                            previousSibling = rangeStartContainer.previousSibling;
                        }
                        else //the caret is considered between nodes so the range container is the element that contains the text nodes.
                        {
                            if(rangeStartOffset > 0) //otherwise, we are at the first character inside a new div (or p), which means at the start of a new line so nothing to remove.
                            {
                                previousSibling = rangeStartContainer.childNodes[rangeStartOffset -1];
                            }
                        }

                        //We update the node and index to modify:
                        if(previousSibling != undefined && previousSibling.nodeType == Node.TEXT_NODE)
                        {
                            textNodeToModify = previousSibling;
                            indexToRemove = previousSibling.textContent.length - 1;
                        } //else, the previous node was not a text node so there has to be a new line between the caret and the previous text so nothing to remove here.
                    }
        
                    if(textNodeToModify != undefined && indexToRemove != -1)
                    {
                        //range.startContainer.textContent = range.startContainer.textContent.substring(rangeStartOffset)
                        var textContent = textNodeToModify.textContent;
                        if(textContent[indexToRemove] == '\t')
                        {
                            var resultingString = textContent.substring(0, indexToRemove); //note: text.substring(0,-1) returns ""
                            resultingString += textContent.substring(indexToRemove + 1); //note: 'aaa'.substring(25) returns ""
                            textNodeToModify.textContent = resultingString;
                        }
                    }
    
                }
            }
        }
        //todo: else.
        instance.TextAreaValueChanged(); //todo: test this.
        e.preventDefault();
            return false;
    }

}, false);", contentEditableDiv, this);//AcceptsReturn, MaxLength
#endif
            }
#if OPENSILVER
            else
            {
                // ---- SIMULATOR ----
                string uid = ((INTERNAL_HtmlDomElementReference)contentEditableDiv).UniqueIdentifier;

                // Set the "data-accepts-return" property (that we have invented) so that the "KeyDown" and "Paste" JavaScript events can retrieve this value:
                // also set the "data-maxlength" and the "data-isreadonly"
                INTERNAL_HtmlDomManager.ExecuteJavaScript($@"
var element = document.getElementByIdSafe(""{uid}"");
element.setAttribute(""data-acceptsreturn"", ""{this.Host.AcceptsReturn.ToString().ToLower()}"");
element.setAttribute(""data-maxlength"", ""{this.Host.MaxLength}"");
element.setAttribute(""data-isreadonly"",""{isReadOnly.ToString().ToLower()}"");
element.setAttribute(""data-acceptstab"", ""{this.Host.AcceptsTab.ToString().ToLower()}"");");

                // Register the "keydown" javascript event:
                INTERNAL_HtmlDomManager.ExecuteJavaScript($@"
var element_OutsideEventHandler = document.getElementByIdSafe(""{uid}"");
element_OutsideEventHandler.addEventListener('keydown', function(e) {{

    var element_InsideEventHandler = document.getElementByIdSafe(""{uid}""); // For some reason we need to get again the reference to the element.
    var acceptsReturn = element_InsideEventHandler.getAttribute(""data-acceptsreturn"");
    var maxLength = element_InsideEventHandler.getAttribute(""data-maxlength"");
    var acceptsTab = element_InsideEventHandler.getAttribute(""data-acceptstab"");

    if (e.keyCode == 13)
    {{
        if(acceptsReturn != ""true"")
        {{
            e.preventDefault();
            return false;
        }}
    }}

    var isAddingTabulation = e.keyCode == 9 && acceptsTab == ""true"";

    if((isAddingTabulation || e.keyCode == 13 || e.keyCode == 32 || e.keyCode > 47) && maxLength != 0)
    {{
        var text = getTextAreaInnerText(element_InsideEventHandler);
        if (!acceptsReturn) {{
            text = text.replace(""\n"", """").replace(""\r"", """");
        }}

        var correctionDueToNewLines = 0;
        if (e.keyCode == 13)
        {{
            ++correctionDueToNewLines; //because adding a new line takes 2 characters instead of 1.
        }}
        if(text.length + correctionDueToNewLines >= maxLength)
        {{
            if (!window.getSelection().toString()) {{
                e.preventDefault();
                return false;
            }}
        }}
    }}

    if(isAddingTabulation)
    {{
        //we need to add '\t' where the cursor is, prevent the event (which would change the focus) and dispatch the event for the text changed:
        var sel, range;
        if (window.getSelection) {{
            sel = window.getSelection();
            if (sel.rangeCount) {{
                range = sel.getRangeAt(0);
                range.deleteContents();
                range.insertNode(document.createTextNode('\t'));
                sel.collapseToEnd();
                range.collapse(false); //for IE
            }}
        }} else if (document.selection && document.selection.createRange) {{
            range = document.selection.createRange();
            range.text = '\t';
            document.selection.collapseToEnd();
        }}
        if (window.IS_EDGE)
        {{
            sel.removeAllRanges();
            sel.addRange(range);
        }}

        instance.TextAreaValueChanged(); //todo: test this.
        e.preventDefault();
            return false;
    }}
}}, false);");//comma added on purpose because we need to get maxLength somehow (see how we did for acceptsReturn).
            }
#endif

            //-----------------------
            // Enforce only Plain Text + prevent line breaks if "AcceptsReturn" is false. This is required because in a ContentEditable div, the user can paste rich text. Furthermore, on IE, pressing Enter will insert a new paragraph.
            //-----------------------
            if (IsRunningInJavaScript())
            {
#if OPENSILVER
                throw new InvalidOperationException();
#elif BRIDGE
                var acceptsReturn = this.Host.AcceptsReturn;
                //todo: shouldn't we add a check for maxlength in the script below like in the other versions of this addEventListenter (in the simulator version below and in the #if !BRIDGE part above)?
                Bridge.Script.Write(@"
{0}.addEventListener('paste', function(e) {
    var isReadOnly= {1};
    if(!isReadOnly)
    {
        var isSingleLine = ({2} !== true); // This is the current value at the time when the event is raised.
        // Chrome supports setting ContentEditable to PlainText-Only, so we try this first:
        {0}.setAttribute('contenteditable', 'PLAINTEXT-ONLY');
        if ({0}.contentEditable === 'plaintext-only') // If setting the attribute worked, we can read it back (in lowercase)
        {
          // --- CHROME: ---
          // Nothing else to do about rich text conversion to plain text.
          // However we still need to remove line breaks if AcceptsReturn is false:
          if (isSingleLine){
            e.preventDefault();
            var content = (e.originalEvent || e).clipboardData.getData('text/plain');
            content = content.replace(/\n/g, '').replace(/\r/g, '');
            document.execCommand('insertText', false, content);
          }
    }
    else
        {
          {0}.setAttribute('contenteditable', 'true');
          if (e.clipboardData){

            // --- FIREFOX: ---
            e.preventDefault();
            var content = (e.originalEvent || e).clipboardData.getData('text/plain');
            if (isSingleLine){
              content = content.replace(/\n/g, '').replace(/\r/g, '');
            }
            document.execCommand('insertText', false, content);
          }
          else if (window.clipboardData){

            // --- INTERNET EXPLORER: ---
            e.preventDefault();
            var content = window.clipboardData.getData('Text');
            if (window.getSelection)
            {
              var newDiv = document.createElement('div');
              if (isSingleLine){
                content = content.replace(/\n/g, '').replace(/\r/g, '');
              }
              content = content.replace(/\r\n/g, '<br />');
              newDiv.innerHTML = content.replace(/\n/g, '<br />');
              var range = window.getSelection().getRangeAt(0);
              range.deleteContents()
              range.insertNode( newDiv );
              //window.getSelection().getRangeAt(0).insertNode( document.createTextNode(content) );
            }
          }
      }
            }
  
}, false);", contentEditableDiv, isReadOnly, acceptsReturn);
                //BRIDGETODO : check the code up
#endif
            }
#if OPENSILVER
            else
            {
                // ---- SIMULATOR ----
                string uid = ((INTERNAL_HtmlDomElementReference)contentEditableDiv).UniqueIdentifier;

                // The simulator uses Chrome, so we can set "ContentEditable" to plain-text only:
                // We still need to prevent prevent line breaks in the pasted text if "AcceptsReturn" is false:
                INTERNAL_HtmlDomManager.ExecuteJavaScript($@"
var element_OutsideEventHandler = document.getElementByIdSafe(""{uid}"");
element_OutsideEventHandler.addEventListener('paste', function(e) {{
    var element_InsideEventHandler = document.getElementByIdSafe(""{uid}""); // For some reason we need to get again the reference to the element.
    var isReadOnly= element_InsideEventHandler.getAttribute(""data-isreadonly"");
    if(isReadOnly !=""true"")
    {{
        var acceptsReturn = element_InsideEventHandler.getAttribute(""data-acceptsreturn"");
        var maxLength = element_InsideEventHandler.getAttribute(""data-maxlength"");
        element_InsideEventHandler.setAttribute(""contentEditable"", ""PLAINTEXT-ONLY"");
        if (acceptsReturn != ""true""){{
            e.preventDefault();
            var content = (e.originalEvent || e).clipboardData.getData('text/plain');
           if(content !== undefined){{
                content = content.replace(/\n/g, '').replace(/\r/g, '');
            }}
            if(maxLength != 0) {{
                var text = getTextAreaInnerText(element_InsideEventHandler);
                //var text = element_InsideEventHandler.innerText;
                if (!acceptsReturn) {{
                    text = text.replace(""\n"", """").replace(""\r"", """");
                }}

                var correctionDueToNewLines = 0;
                var textBoxTextLength = text.length + correctionDueToNewLines;
                var lengthComparison = maxLength - (content.length + textBoxTextLength);
                if(lengthComparison < 0) {{
                    content = content.substr(0, content.length+lengthComparison);
                }}
            }}
            document.execCommand('insertText', false, content);
        
        }}
    }}
    

}}, false);");
            }
#endif

            return(outerDiv);
        }
Esempio n. 10
0
        internal static object ExecuteJavaScript_SimulatorImplementation(string javascript, bool runAsynchronously, bool noImpactOnPendingJSCode = false, params object[] variables)
        {
            //---------------
            // Due to the fact that it is not possible to pass JavaScript objects between the simulator JavaScript context
            // and the C# context, we store the JavaScript objects in a global dictionary inside the JavaScript context.
            // This dictionary is named "jsObjRef". It associates a unique integer ID to each JavaScript
            // object. In C# we only manipulate those IDs by manipulating instances of the "JSObjectReference" class.
            // When we need to re-use those JavaScript objects, the C# code passes to the JavaScript context the ID
            // of the object, so that the JavaScript code can retrieve the JavaScript object instance by using the
            // aforementioned dictionary.
            //---------------

            // Verify the arguments:
            if (noImpactOnPendingJSCode && runAsynchronously)
            {
                throw new ArgumentException("You cannot set both 'noImpactOnPendingJSCode' and 'runAsynchronously' to True. The 'noImpactOnPendingJSCode' only has meaning when running synchronously.");
            }

            // Make sure the JS to C# interop is set up:
            if (!IsJavaScriptCSharpInteropSetUp)
            {
#if OPENSILVER
                if (Interop.IsRunningInTheSimulator_WorkAround)
                {
#endif
                // Adding a property to the JavaScript "window" object:
                dynamic jsWindow = INTERNAL_HtmlDomManager.ExecuteJavaScriptWithResult("window");
                jsWindow.SetProperty("onCallBack", new OnCallBack(CallbacksDictionary));
#if OPENSILVER
            }
            else
            {
                OnCallBack.SetCallbacksDictionary(CallbacksDictionary);
            }
#endif
                IsJavaScriptCSharpInteropSetUp = true;
            }

            string unmodifiedJavascript = javascript;

            // If the javascript code has references to previously obtained JavaScript objects,
            // we replace those references with calls to the "document.jsObjRef"
            // dictionary.
            // Note: we iterate in reverse order because, when we replace ""$" + i.ToString()", we
            // need to replace "$10" before replacing "$1", otherwise it thinks that "$10" is "$1"
            // followed by the number "0". To reproduce the issue, call "ExecuteJavaScript" passing
            // 10 arguments and using "$10".
            for (int i = variables.Length - 1; i >= 0; i--)
            {
                var variable = variables[i];
                if (variable is INTERNAL_JSObjectReference)
                {
                    //----------------------
                    // JS Object References
                    //----------------------

                    var    jsObjectReference = (INTERNAL_JSObjectReference)variable;
                    string jsCodeForAccessingTheObject;

                    if (jsObjectReference.IsArray)
                    {
                        jsCodeForAccessingTheObject = $@"document.jsObjRef[""{jsObjectReference.ReferenceId}""][{jsObjectReference.ArrayIndex}]";
                    }
                    else
                    {
                        jsCodeForAccessingTheObject = $@"document.jsObjRef[""{jsObjectReference.ReferenceId}""]";
                    }

                    javascript = javascript.Replace("$" + i.ToString(), jsCodeForAccessingTheObject);
                }
                else if (variable is INTERNAL_HtmlDomElementReference)
                {
                    //------------------------
                    // DOM Element References
                    //------------------------

                    string id = ((INTERNAL_HtmlDomElementReference)variable).UniqueIdentifier;
                    javascript = javascript.Replace("$" + i.ToString(), $@"document.getElementByIdSafe(""{id}"")");
                }
                else if (variable is INTERNAL_SimulatorJSExpression)
                {
                    //------------------------
                    // JS Expression (simulator only)
                    //------------------------

                    string expression = ((INTERNAL_SimulatorJSExpression)variable).Expression;
                    javascript = javascript.Replace("$" + i.ToString(), expression);
                }
                else if (variable is Delegate)
                {
                    //-----------
                    // Delegates
                    //-----------

                    Delegate callback = (Delegate)variable;

                    // Add the callback to the document:
                    int callbackId = ReferenceIDGenerator.GenerateId();
                    CallbacksDictionary.Add(callbackId, callback);

                    var isVoid = callback.Method.ReturnType == typeof(void);

                    // Change the JS code to point to that callback:
                    javascript = javascript.Replace("$" + i.ToString(), string.Format(
                                                        @"(function() {{ return document.eventCallback({0}, {1}, {2});}})", callbackId,
#if OPENSILVER
                                                        Interop.IsRunningInTheSimulator_WorkAround ? "arguments" : "Array.prototype.slice.call(arguments)",
#elif BRIDGE
                                                        "Array.prototype.slice.call(arguments)",
#endif
                                                        (!isVoid).ToString().ToLower()
                                                        ));

                    // Note: generating the random number in JS rather than C# is important in order
                    // to be able to put this code inside a JavaScript "for" statement (cf.
                    // deserialization code of the JsonConvert extension, and also ZenDesk ticket #974)
                    // so that the "closure" system of JavaScript ensures that the number is the same
                    // before and inside the "setTimeout" call, but different for each iteration of the
                    // "for" statement in which this piece of code is put.
                    // Note: we store the arguments in the jsObjRef that is inside
                    // the JS context, so that the user can access them from the callback.
                    // Note: "Array.prototype.slice.call" will convert the arguments keyword into an array
                    // (cf. http://stackoverflow.com/questions/960866/how-can-i-convert-the-arguments-object-to-an-array-in-javascript)
                    // Note: in the command above, we use "setTimeout" to avoid thread/locks problems.
                }
                else if (variable == null)
                {
                    //--------------------
                    // Null
                    //--------------------

                    javascript = javascript.Replace("$" + i.ToString(), "null");
                }
                else
                {
                    //--------------------
                    // Simple value types or other objects
                    // (note: this includes objects that
                    // override the "ToString" method, such
                    // as the class "Uri")
                    //--------------------

                    javascript = javascript.Replace("$" + i.ToString(), INTERNAL_HtmlDomManager.ConvertToStringToUseInJavaScriptCode(variable));
                }
            }

            UnmodifiedJavascriptCalls.Add(unmodifiedJavascript);

            // Change the JS code to call ShowErrorMessage in case of error:
            string errorCallBackId = IndexOfNextUnmodifiedJSCallInList.ToString();
            ++IndexOfNextUnmodifiedJSCallInList;

            // Surround the javascript code with some code that will store the
            // result into the "document.jsObjRef" for later
            // use in subsequent calls to this method
            int referenceId = ReferenceIDGenerator.GenerateId();
            javascript = $"document.callScriptSafe(\"{referenceId.ToString()}\",\"{INTERNAL_HtmlDomManager.EscapeStringForUseInJavaScript(javascript)}\",{errorCallBackId})";

            // Execute the javascript code:
            object value = null;
            if (!runAsynchronously)
            {
                value = CastFromJsValue(INTERNAL_HtmlDomManager.ExecuteJavaScriptWithResult(javascript, noImpactOnPendingJSCode: noImpactOnPendingJSCode));
            }
            else
            {
                INTERNAL_HtmlDomManager.ExecuteJavaScript(javascript);
            }

            var objectReference = new INTERNAL_JSObjectReference()
            {
                Value       = value,
                ReferenceId = referenceId.ToString()
            };

            return(objectReference);
        }